/*
 *  linux/drivers/s390/net/lcs.c
 *
 *  Linux for S/390 Lan Channel Station Network Driver
 *
 *
 *  Copyright (C)  1999-2001 IBM Deutschland Entwicklung GmbH, IBM Corporation
 *    Author(s): DJ Barrow (djbarrow@de.ibm.com,barrow_dj@yahoo.com) 
 *               Frank Pavlic (pavlic@de.ibm.com)
 *
 *    $Revision: 1.90 $    $Date: 2002/03/01 17:20:11 $
 * 
 * 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.
 *
 */


/*
 	Thanks to Uli Hild, Martin Schwidefsky,
	& Ingo who probably will never let it down that 
	he found the braindamage with my offset calculations on 
	the IO buffers.
	( for quality of effort, advise, support & sanity checking )
	Alex, Boas & the rest of the team for the opportunity to work here.

DRIVER OPERATION OVERVIEW README FIRST
======================================

The lcs driver currently supports Ethernet & Token Ring, FDDI is still
experimental.

The lan channel station(lcs) network interface has two channels one read
channel one  write channel. This is very similar to the S390 CTC interface

The read channel is recognised by having an even cuu number & model 0x3088
The write channel is recognised by the formula write cuu=read cuu number + 1
& also a model of 0x3088 only certain cuu models are supported so as not to
clash with a CTC control unit model.

The driver always has a read outstanding on the read subchannel this is used
to receive both command replys & network packets ( these are differenciated
by checking the type field in the lcs header structure ), network packets
may come in during the startup & shutdown sequence these have to be discarded.
During normal network I/O the driver will intermittently retry reads to
permanently keep reads outstanding on the read channel if an -EBUSY or
similar occurs (otherwise the driver would stop receiving network packets).

The channel progam used work as described below
===============================================
The write subchannel has a hack in /net/core/skbuff.c which makes
sure there is enough head & tailroom to write packets directly out of the
socket buffer ( If their isn't the code will still work ). 

If the write subchannel is busy network packets are
aggregated together into a & queued for transmission, the reason I'm doing
this is to minimise start subchannels & interrupts which I have been told
& have proven to be very expensive.

e.g. a ping -f -s 1400 -c 20000 <gateway_addr> >/dev/null used take 40 secs,
on my machine & a ping -f -s 100 -c 20000 <gateway_addr> >/dev/null took
27 secs, over token ring with a single buffer this has improved a lot with
the queueing technique (I also made sure that the delay wasn't caused by
waiting for the free token).

The new program works as follows
================================
The read & channel program consists of of 8 read ccw commands 
followed by a transfer in channel back to the 1st read ccw. 
A moving suspend bit is switched on in the read
ccw of the last buffer processed with pci's switched on in the
remaining ccw's. This way I recieve buffers with pci's without
having to issue excess resumes or ssch's & only
need to issue a resume if the read program gets suspended when
all the buffers are filled.

The write program consists of 4 to 9 write ccw commands folled
by a transfer in channel back to the 1st write ccw.
All unfilled buffers all have the suspend bit on & the
suspend bit is taken off when buffers are either filled or partially
filled & the write channel is no longer busy.

The lan channel is started as follows
1) A halt subchannel is issued to the read & write subchannel
2) A startup primitive is sent to the lan & the reply is read from the
read subchannel.
3) Every possible relative adapter number issued a startlan primative
with attempts to configure it as token ring ethernet & fddi at some stage
in the boot sequence or when the module is being inserted.
The relative adapter number is normally equal to the low byte of the read
channel cuu/2 & hence the reason for the hint field to speed up lcs_detect.
5) On success of a startlan we  know whether the osa card is configured as
token ring,ethernet or fddi,a lanstat primative is issued to get the
mac address.
6) The device is now ready for network io.

The shutdown is similar to the startup just a stoplan primative followed
by a shutdown.

The driver instance globals lcs_drvr_globals are held in the priv element
of the device structure & these in turn point to a lcs_chan_globals
structure for the read & write channel. The read & write chan_globals &
driver globals have their own state machine, so that the interrupt handler
can make decisions on whether to send packets read to the network layer, or
whether it is should wake up a process currently issuing lan commands.
*/

#define TRUE 1
#define FALSE 0

#include <linux/version.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/errno.h>
#include <linux/init.h>
#include <asm/system.h>
#include <asm/io.h>
#include <asm/irq.h>
#include <linux/netdevice.h>
#if LINUX_VERSION_CODE<KERNEL_VERSION(2,3,0)
#include <linux/kcomp.h>
#endif

#if CONFIG_NET_ETHERNET
#include <linux/etherdevice.h>
extern int ethif_probe (struct net_device *dev);
#endif
#ifdef CONFIG_TR
#include <linux/trdevice.h>
extern int trif_probe (struct net_device *dev);
#endif
#ifdef CONFIG_FDDI
#include <linux/fddidevice.h>
extern int fddiif_probe (struct net_device *dev);
#endif
#include <asm/irq.h>
#include <asm/queue.h>
#define LCS_CHANDEV CONFIG_CHANDEV
#if LCS_CHANDEV
#include <asm/chandev.h>
#endif
#ifdef CONFIG_IP_MULTICAST
#include <linux/inetdevice.h>
#include <net/arp.h>
#include <linux/in.h>
#include <linux/igmp.h>
#endif
#include <net/pkt_sched.h>
#include <net/sock.h>
#include <net/dst.h>

#define LCS_USE_IDALS (LINUX_VERSION_CODE>KERNEL_VERSION(2,2,16))
#if (!CONFIG_ARCH_S390X) || (LCS_USE_IDALS)
#define lcs_dev_alloc_skb(length) dev_alloc_skb((length))
#endif
#if LCS_USE_IDALS
#  include <asm/idals.h>
#else
#define set_normalized_cda(ccw, addr) ((ccw)->cda = (addr))
#define clear_normalized_cda(ccw)
#endif

#if (LINUX_VERSION_CODE>=KERNEL_VERSION(2,2,18))
#define LCS_DEBUG 1
#else
#define LCS_DEBUG 0
#endif
#define LCS_PRINTK_DEBUG   0
#define LCS_ZERO_COPY_READ 0

#if LCS_DEBUG && !LCS_PRINTK_DEBUG
#include <asm/debug.h>
#endif

#if !defined(CONFIG_NET_ETHERNET)&&!defined(CONFIG_TR)&&!defined(CONFIG_FDDI)
#error Cannot compile lcs.c without some net devices switched on.
#endif

#define lcs_inline		// inline

#if LINUX_VERSION_CODE<=KERNEL_VERSION(2,2,16)
#define lcs_daemonize(name,mask,use_init_fs) s390_daemonize(name,mask)
#else
#define lcs_daemonize(noargs...)
#endif

/* for the proc filesystem */
static const char *cardname = "S390 Lan Channel Station Interface";

/* lcs ccw command codes */

enum {
    ccw_write = 0x1,
    ccw_read = 0x2,
    ccw_read_backward = 0xc,
    ccw_control = 0x3,
    ccw_sense = 0x4,
    ccw_sense_id = 0xe4,
    ccw_transfer_in_channel0 = 0x8,
    ccw_transfer_in_channel1 = 0x8,
    ccw_set_x_mode = 0xc3,	// according to uli's lan notes
    ccw_nop = 0x3		// according to uli's notes again
	// n.b. ccw_control clashes with this
	// so I presume its a special case of
	// control
};

/* startup & shutdown primatives */

typedef enum {
    lcs_startlan = 1,
    lcs_stoplan = 2,
    lcs_lanstat = 4,
    lcs_startup = 7,
    lcs_shutdown = 8,
#ifdef CONFIG_IP_MULTICAST
    lcs_qipassist = 0xb2,
    lcs_setipm = 0xb4,
    lcs_delipm = 0xb5
#endif
} lcs_cmd;

typedef enum {
    command_reject = 0x80,
    intervention_required = 0x40,
    bus_out_check = 0x20,
    equipment_check = 0x10,
    interface_disconnect = 0x01,
} lcs_sense_byte_0;

typedef enum {
    resetting_event = 0x0080,
    device_online = 0x0020,
} lcs_sense_byte_1;

/* The type of packet we are transmitting or receiving from the lcs */
typedef enum {
    lcs_control_frame_type = 0,	/* for startup shutdown etc */
    lcs_enet_type = 1,		/* the rest are for normal network io */
    lcs_token_ring_type = 2,
    lcs_fddi_type = 7,
    lcs_autodetect_type = -1,
} lcs_frame_type;

#define LCS_ILLEGAL_OFFSET 0xffff

static int use_hw_stats = 0;
static int checksum_received_ip_pkts = 0;
#if !LCS_CHANDEV
/* Max number of cuu models we can detect */
#define LCS_CURR_NUM_MODELS   4
#define LCS_ADDITIONAL_MODELS 12
#define LCS_ADDITIONAL_MODELS_X2_STR "24"
#define LCS_MAX_NUM_MODELS  (LCS_CURR_NUM_MODELS+LCS_ADDITIONAL_MODELS)
/* max number of parms that can be sent to lcs_setup */
#define LCS_MAX_NUM_PARMS ((LCS_MAX_NUM_MODELS<<1)+2)
/* configuration parms for the lcs card */
typedef struct {
    int cu_model;
    int max_rel_adapter_no;
} lcs_model_info;

/* The models supported currently used in probing */
#define LCS_CUTYPE 0x3088

/* default configuration parms */

static lcs_model_info lcs_models[LCS_CURR_NUM_MODELS]
    __attribute__ ((section (".data"))) = {
    {0x1, 15},			
    /* P390/Planter 3172 emulation assume maximum 16 to be safe. */
    {0x8, 15},			
    /* 3172/2216 Paralell the 2216 allows 16 ports per card the */
    /* the original 3172 only allows 4 we will assume the max of 16 */
    {0x60, 1},			
    /* Only 2 ports allowed on OSA2 cards model 0x60 */
    {0x1F, 15},			
    /* 3172/2216 Escon serial the 2216 allows 16 ports per card the */
    /* the original 3172 only allows 4 we will assume the max of 16 */
};
static int additional_model_info[LCS_ADDITIONAL_MODELS << 1] = { 0, };
/* Set this to one to stop autodetection */
static int noauto = 0, ignore_sense = 0;

/*
 * The lcs header 
 * offset=the offset of the next cmd/network packet 
 * type=command ethernet token ring fddi frame 
 * slot=relative adapter no
 */

#define LCS_MAX_FORCED_DEVNOS 12
#define LCS_MAX_FORCED_DEVNOS_X2_STR "24"
typedef struct {
    int devno;
    int portno;
} lcs_dev_portno_pair;
static int devno_portno_pairs[LCS_MAX_FORCED_DEVNOS << 1] =
    {[0 ... ((LCS_MAX_FORCED_DEVNOS << 1) - 1)] 2, };
static lcs_dev_portno_pair *lcs_dev_portno =
    (lcs_dev_portno_pair *) & devno_portno_pairs[0];
#endif

#define lcs_header           \
u16		offset;      \
u8		type;        \
u8		slot;

typedef struct {
lcs_header} lcs_header_type __attribute__ ((packed));

enum {
    lcs_390_initiated,
    lcs_lgw_initiated
};

/*
 * lcs startup & shutdown commands 
 */
#define lcs_base_cmd         \
u8		cmd_code;    \
u8		initiator;   \
u16		sequence_no; \
u16		return_code; \

typedef struct {
    lcs_header lcs_base_cmd u8 lan_type;
    u8 rel_adapter_no;
    u16 parameter_count;
    u8 operator_flags[3];
    u8 reserved[3];
    u8 command_data[0];
} lcs_std_cmd __attribute__ ((packed));

typedef struct {
    lcs_header lcs_base_cmd u16 unused1;
    u16 buff_size;
    u8 unused2[6];
} lcs_startup_cmd __attribute__ ((packed));

#define LCS_ADDR_LEN 6

#ifdef CONFIG_IP_MULTICAST
typedef struct {
    u32 ip_addr;
    u8 mac_address[LCS_ADDR_LEN];
    u8 reserved[2];
} lcs_ip_mac_addr_pair __attribute__ ((packed));

typedef enum {
    ipm_set_required,
    ipm_delete_required,
    ipm_on_card
} lcs_ipm_state;
typedef struct lcs_ipm_list lcs_ipm_list;
struct lcs_ipm_list {
    struct lcs_ipm_list *next;
    lcs_ip_mac_addr_pair ipm;
    lcs_ipm_state ipm_state;
};

#define MAX_IP_MAC_PAIRS      32
typedef struct {
    lcs_header lcs_base_cmd u8 lan_type;
    u8 rel_adapter_no;
    /* OSA only will only support one IP Multicast entry at a time */
    u16 num_ipm_pairs;
    u16 ip_assists_supported;	/* returned by OSA  */
    u16 ip_assists_enabled;	/* returned by OSA */
    u16 version;		/* IP version i.e. 4 */
    lcs_ip_mac_addr_pair ip_mac_pair[MAX_IP_MAC_PAIRS];
    u32 response_data;
} lcs_ipm_ctlmsg __attribute__ ((packed));
typedef struct {
    lcs_header lcs_base_cmd u8 lan_type;
    u8 rel_adapter_no;
    u16 num_ip_pairs;		/* should be 0 */
    u16 ip_assists_supported;	/* returned by OSA  */
    u16 ip_assists_enabled;	/* returned by OSA */
    u16 version;		/* IP version i.e. 4 */
} lcs_qipassist_ctlmsg __attribute__ ((packed));

typedef enum {
    /* Not supported by LCS */
    lcs_arp_processing = 0x0001,
    lcs_inbound_checksum_support = 0x0002,
    lcs_outbound_checksum_support = 0x0004,
    lcs_ip_frag_reassembly = 0x0008,
    lcs_ip_filtering = 0x0010,
    /* Supported by lcs 3172 */
    lcs_ip_v6_support = 0x0020,
    lcs_multicast_support = 0x0040
} lcs_assists;
#define LANCMD_DEFAULT_MCAST_PARMS ,0,NULL
#else
#define LANCMD_DEFAULT_MCAST_PARMS
#endif

typedef struct {
    lcs_header lcs_base_cmd u8 lan_type;
    u8 rel_adapter_no;
    u8 unused1[10];
    u8 mac_address[LCS_ADDR_LEN];
    u32 num_packets_deblocked;
    u32 num_packets_blocked;
    u32 num_packets_tx_on_lan;
    u32 num_tx_errors_detected;
    u32 num_tx_packets_disgarded;
    u32 num_packets_rx_from_lan;
    u32 num_rx_errors_detected;
    u32 num_rx_discarded_nobuffs_avail;
    u32 num_rx_packets_too_large;
} lcs_lanstat_reply __attribute__ ((packed));

/* This buffer sizes are used by MVS so they should be reasonable */
#define LCS_IOBUFFSIZE     0x5000
#define LCS_NUM_TX_BUFFS    8
#define LCS_NUM_RX_BUFFS    8

#define LCS_INVALID_LOCK_OWNER            -1

#if LINUX_VERSION_CODE<KERNEL_VERSION(2,2,18)
typedef struct wait_queue *wait_queue_head_t;
#endif
#if LINUX_VERSION_CODE<KERNEL_VERSION(2,4,0)
typedef dev_info_t s390_dev_info_t;
#endif

typedef enum {
    chan_dead,
    chan_idle,
    chan_busy,
    chan_starting_up
} lcs_chan_busy_state;

#define LCS_MAGIC			0x05A22A05	/* OSA2 to you */
#define LCS_CHAN_GLOBALS(num_io_buffs)      \
u32                  irq_allocated_magic;   \
u16 subchannel;                             \
u16 devno;                                  \
struct lcs_drvr_globals *drvr_globals;      \
atomic_t             sleeping_on_io;        \
unsigned long        flags;                 \
int                  rc;                    \
int                  lock_owner;            \
int                  lock_cnt;              \
wait_queue_head_t    wait;                  \
struct  tq_struct    retry_task;            \
devstat_t	     devstat;               \
lcs_chan_busy_state  chan_busy_state;       \
ccw1_t               ccw[num_io_buffs+1];   \


typedef struct {
    LCS_CHAN_GLOBALS (0)
} lcs_chan_globals;

typedef struct {
    LCS_CHAN_GLOBALS (LCS_NUM_RX_BUFFS)
#if LCS_ZERO_COPY_READ
    struct sk_buff *skb_buff[LCS_NUM_RX_BUFFS];
#else
    u8 *iobuff[LCS_NUM_RX_BUFFS];
#endif
    lcs_std_cmd *lancmd_reply_ptr;	/* only used by read channel */
    u32 rx_idx;
    u8 pad[0] __attribute__ ((aligned (8)));	/* so the next structure
						   will be aligned */
} lcs_read_globals;

typedef struct {
    LCS_CHAN_GLOBALS (LCS_NUM_TX_BUFFS)
    u8 *iobuff[LCS_NUM_TX_BUFFS];
    u32 prepare_idx;
    u32 tx_idx;
    unsigned long pkts_still_being_txed;
    unsigned long bytes_still_being_txed;
    struct tq_struct resume_task;
    int resume_queued;
    int resume_loopcnt;
    uint64_t last_lcs_txpacket_time;
    uint64_t last_resume_time;
    uint64_t adjusted_last_bytes_still_being_txed;
    u32 buffsfilled;
} lcs_write_globals;

#define LCS_READ_ALLOCSIZE      LCS_IOBUFFSIZE

/*
 * drvr_globals main state machine 
 */
typedef enum {
    lcs_idle,
    lcs_halting_subchannels,
    lcs_doing_io,
    lcs_idle_requesting_channels_for_lancmds,
    lcs_requesting_channels_for_lancmds,
    lcs_got_write_channel_for_lancmds,
    lcs_doing_lancmds,
    lcs_interrupted
} lcs_state;

enum {
    lcs_invalid_adapter_no = -1
};

typedef struct lcs_drvr_globals lcs_drvr_globals;
struct lcs_drvr_globals {
    struct lcs_drvr_globals *next;
    struct net_device *dev;
    atomic_t usage_cnt;		/* used by drvr_globals_valid &
				   lcs_usage_free_drvr_globals */
    u8 lan_type;
    u8 cmd_code;
    unsigned short (*lan_type_trans) (struct sk_buff * skb,
				      struct net_device * dev);
    wait_queue_head_t lanstat_wait;	/* processes asleep awaiting
					   lanstat results */
    int doing_lanstat;
    int up;
    lcs_state state;
    lcs_read_globals *read;
    lcs_write_globals *write;
    u16 sequence_no;
    s8 rel_adapter_no;
    int slow_hw;
/* To insure only one kernel thread runs at a time */
    atomic_t kernel_thread_lock;
    int (*kernel_thread_routine) (lcs_drvr_globals * drvr_globals);
    struct tq_struct kernel_thread_task;
    u16 lgw_sequence_no;	/* this isn't required just being thorough */
    int reset_retry_cnt;
    struct net_device_stats stats __attribute__ ((aligned (4)));
#ifdef CONFIG_IP_MULTICAST
    spinlock_t ipm_lock;
    lcs_ipm_list *ipm_list;
#endif
    /* So I can use atomic atomic_t for stats if required  */
    u8 pad[0] __attribute__ ((aligned (8)));
    /* CCW's need 8 byte alignment ( Thanks Martin ) */
} __attribute__ ((aligned (8)));

/* A token ring header can be 40 bytes if you include the llc & rcf so
 * we leave a small bit extra.
 */

#define LCS_TX_HIWATERMARK          LCS_IOBUFFSIZE-60

#define LCS_ALLOCSIZE \
	(sizeof(lcs_drvr_globals)+sizeof(lcs_read_globals)+ \
	 sizeof(lcs_write_globals))

/* Function prototypes */
void lcs_rxpacket (lcs_drvr_globals * drvr_globals, u8 * start_buff
#if LCS_ZERO_COPY_READ
		   , u32 curr_idx
#endif
    );
int lcs_txpacket (struct sk_buff *skb, struct net_device *dev);
static int lcs_detect (lcs_drvr_globals * drvr_globals, s16 forced_port_no,
		       u8 hint_port_no, u8 max_port_no,
		       lcs_frame_type frame_type, u8 * mac_address,
		       int retrycnt);
static int lcs_check_reset (lcs_chan_globals * chan_globals);
static void lcs_restartreadio (lcs_drvr_globals * drvr_globals);
static void lcs_restartwriteio (lcs_drvr_globals * drvr_globals);
static void lcs_queued_restartreadio (lcs_drvr_globals * drvr_globals);
static void lcs_queued_restartwriteio (lcs_drvr_globals * drvr_globals);
static void lcs_resume_writetask (lcs_write_globals * write);
#ifdef MODULE
void cleanup_module (void);
#endif

#ifdef CONFIG_IP_MULTICAST
static int lcs_fix_multicast_list (lcs_drvr_globals * drvr_globals);
#endif

#define lcs_debug_initmessage      char *message=NULL;
#define lcs_debug_setmessage(string) message=string

#if LCS_DEBUG
#if LCS_PRINTK_DEBUG
#define lcs_debug_event(level,args...)      printk(##args)
#define lcs_debug_exception(level,args...)  printk(##args)
#define lcs_bad_news(args...)         printk(KERN_CRIT ##args);
#define lcs_good_news(args...)         printk(##args);
#else
debug_info_t *lcs_id = NULL;
#define lcs_debug_event(args...)      debug_sprintf_event(lcs_id,##args)
#define lcs_debug_exception(args...)  debug_sprintf_exception(lcs_id,##args)
#define lcs_bad_news(args...)                 \
{                                             \
	lcs_debug_exception(0,##args);        \
	printk(KERN_CRIT ##args);             \
}

#define lcs_good_news(args...)                \
{                                             \
	lcs_debug_event(0,##args);            \
	printk(##args);                       \
}
#endif

#define lcs_debugchannel(message,chan_globals) \
lcs_debugchannel_func((message),(lcs_chan_globals *)(chan_globals))

void
lcs_debugchannel_func (char *message, lcs_chan_globals * chan_globals)
{
    lcs_drvr_globals *drvr_globals;

    if (message) {
	lcs_debug_event (0, "lcs: %s\n", message);
    }
    lcs_debug_event (0, "lcs  irq=%04x devno=%04x %s\n",
		     (int) chan_globals->subchannel, (int) chan_globals->devno,
		     (message ? message : ""));
    lcs_debug_event (0, "chan globals=%p\n", chan_globals);
    if (chan_globals) {
	drvr_globals = chan_globals->drvr_globals;
	if (drvr_globals) {
	    if (drvr_globals->read != (lcs_read_globals *) chan_globals &&
		drvr_globals->write != (lcs_write_globals *) chan_globals)
		lcs_debug_event (0, "drvr globals inconsistent\n");
	    lcs_debug_event (0, "drvr_globals=%p:state=%d\n", drvr_globals,
			     drvr_globals->state);
	    lcs_debug_event (0,
			     "chan globals sleeping_on_io=%d"
			     " busy_state=%d lock_owner=%d lock_cnt=%d\n",
			     atomic_read (&chan_globals->sleeping_on_io),
			     chan_globals->chan_busy_state,
			     chan_globals->lock_owner, chan_globals->lock_cnt);
	}
    }
    lcs_debug_exception (0, "leaving lcs_debugchannel\n");
}

void
lcs_debug_display_read_buff (char *message, lcs_std_cmd * lancmd_reply_ptr)
{
    int *read_buff = (int *) lancmd_reply_ptr;

    lcs_debug_event (1, "%s %08x %08x %08x\n", message,
		     read_buff[0], read_buff[1], read_buff[2]);
}

#else
#define lcs_debug_event(noargs...)
#define lcs_debug_exception(noargs...)
#define lcs_bad_news(args...)  printk(KERN_CRIT ##args)
#define lcs_good_news(args...)  printk(##args)
#define lcs_debugchannel(message,chan_globals)   \
printk("lcs  irq=%04x devno=%04x %s\n",(int)chan_globals->subchannel, \
(int)chan_globals->devno,(message ? message:""))
#define lcs_debug_display_read_buff(noargs...)
#endif

#if (!LCS_USE_IDALS) && (CONFIG_ARCH_S390X)
static inline struct sk_buff *
lcs_dev_alloc_skb (unsigned int length)
{
    struct sk_buff *skb;

    skb = alloc_skb (length + 16, GFP_ATOMIC | GFP_DMA);
    if (skb)
	skb_reserve (skb, 16);
    return skb;
}
#endif

static void lcs_inline
lcs_resetstate (lcs_drvr_globals * drvr_globals)
{
    drvr_globals->state = lcs_idle;
}

static __inline__ int
netif_is_busy (struct net_device *dev)
{
#if LINUX_VERSION_CODE<KERNEL_VERSION(2,3,45)
    return (dev->tbusy);
#else
    return (test_bit (__LINK_STATE_XOFF, &dev->flags));
#endif
}

/* 
 * Primatives used to put the process to sleep or wake it up during
 * startup or shutdown.
 */
static lcs_inline void
lcs_initqueue_func (lcs_chan_globals * chan_globals)
{
    atomic_set (&chan_globals->sleeping_on_io, TRUE);
    chan_globals->rc = 0;
#if LINUX_VERSION_CODE>=KERNEL_VERSION(2,3,0)
    init_waitqueue_head (&chan_globals->wait);
#else
    chan_globals->wait = NULL;
#endif
}

#define lcs_initqueue(chan_globals) \
	lcs_initqueue_func((lcs_chan_globals *)chan_globals)

static void
lcs_wakeup_func (lcs_chan_globals * chan_globals, int rc)
{
    lcs_debug_event (2, "lcs_wakeup called sch=%04x rc=%d\n,",
		     (int) chan_globals->subchannel, rc);
    atomic_set (&chan_globals->sleeping_on_io, FALSE);
    if (chan_globals->rc == 0)
	chan_globals->rc = rc;
    wake_up (&chan_globals->wait);
}

#define TOD_SHIFT_TO_USECS 12
static inline uint64_t
lcs_get_tod (void)
{
    uint64_t cc;

    asm volatile ("STCK %0":"=m" (cc));
    return (cc);
}

#define lcs_wakeup(chan_globals,rc) \
	lcs_wakeup_func((lcs_chan_globals *)chan_globals,(rc))
static int
lcs_sleepon_func (lcs_chan_globals * chan_globals, int extratime)
{
    /*
       We have a miniscule chance of sleeping here for 1 second
       we have to unfortunately live with it as down_interruptible
       dosen't wake up for timers just signals.
     */
    int rc = 0;

    lcs_debug_event (2, "lcs_sleepon called sch=%04x\n,",
		     (int) chan_globals->subchannel);
    sleep_on_timeout (&chan_globals->wait, HZ);
    if (atomic_read (&chan_globals->sleeping_on_io) && chan_globals->rc == 0
	&& extratime) {
	lcs_bad_news("lcs_sleepon network card taking time responding "
		     "irq=%04x devno=%04x,\n"
		     "please be patient, ctrl-c will exit if shell prompt "
		     "is available.\n",
	     (int) chan_globals->subchannel, (int) chan_globals->devno);
	interruptible_sleep_on_timeout (&chan_globals->wait, extratime * HZ);
    }
    if (atomic_read (&chan_globals->sleeping_on_io) && chan_globals->rc == 0) {
	chan_globals->drvr_globals->state = lcs_interrupted;
	rc = (current->sigpending ? -EINTR : -ETIMEDOUT);

    }
    if (chan_globals->rc)
	rc = chan_globals->rc;
    lcs_debug_event (2, "lcs_sleepon sch=%04x devno=%04x rc=%d\n",
		     (int) chan_globals->subchannel,
		     (int) chan_globals->devno, rc);
    return (rc);

}

#define lcs_sleepon(chan_globals,extratime) \
	lcs_sleepon_func((lcs_chan_globals *)chan_globals,extratime)

static void
lcs_rxerror (char *message, lcs_drvr_globals * drvr_globals)
{
    lcs_read_globals *read = NULL;
    struct net_device_stats *stats = NULL;
    if (drvr_globals) {
	read = drvr_globals->read;
	stats = &drvr_globals->stats;
    }
    if (use_hw_stats == FALSE && stats)
	stats->rx_errors++;
    lcs_debugchannel (message, read);
}

static void
lcs_chan_lock_func (lcs_chan_globals * chan_globals)
{
    eieio ();
    if (chan_globals->lock_owner != smp_processor_id ()) {
	s390irq_spin_lock_irqsave (chan_globals->subchannel,
				   chan_globals->flags);
	chan_globals->lock_cnt = 1;
	chan_globals->lock_owner = smp_processor_id ();

    } else
	chan_globals->lock_cnt++;
    if (chan_globals->lock_cnt < 0 || chan_globals->lock_cnt > 100) {
	lcs_bad_news ("bad lock_cnt %d in lcs_chan_lock chan_globals=%p"
		      " devno=%04x returnaddrs=%p,%p cpu=%d\n",
		      chan_globals->lock_cnt, chan_globals,
		      (int) chan_globals->devno,
		      __builtin_return_address (0),
		      __builtin_return_address (1), smp_processor_id ());
	chan_globals->lock_cnt = 1;
    }
}

static void
lcs_chan_unlock_func (lcs_chan_globals * chan_globals)
{
    if (chan_globals->lock_cnt <= 0) {
	lcs_bad_news ("bad lock_cnt %d in lcs_chan_unlock chan_globals=%p"
		      " devno=%04x returnaddrs=%p,%p cpu=%d\n",
		      chan_globals->lock_cnt, chan_globals,
		      (int) chan_globals->devno,
		      __builtin_return_address (0),
		      __builtin_return_address (1), smp_processor_id ());
	chan_globals->lock_cnt = 1;
    }

    if (--chan_globals->lock_cnt == 0) {
	chan_globals->lock_owner = LCS_INVALID_LOCK_OWNER;
	s390irq_spin_unlock_irqrestore (chan_globals->subchannel,
					chan_globals->flags);
    }

}

#define lcs_chan_lock(chan_globals) \
	lcs_chan_lock_func((lcs_chan_globals *)chan_globals)
#define lcs_chan_unlock(chan_globals) \
	lcs_chan_unlock_func((lcs_chan_globals *)chan_globals)

/*
 * A little utility routine to make a ccw chain.
 */
static void
initccws (ccw1_t * ccws, u8 * iobuff[], int num_ccws, u8 cmd_code, u16 count,
	  u8 flags)
{
    int cnt;
    ccw1_t *origccws = ccws;
    memset (ccws, 0, sizeof (ccw1_t) * num_ccws + 1);
    for (cnt = 0; cnt < num_ccws; cnt++, ccws++) {
	ccws->cmd_code = cmd_code;
	ccws->count = count;
	ccws->flags = flags;
	/* ccw addresses are 32 bit */
	(u32) ccws->cda = (u32) virt_to_phys (iobuff[cnt]);
    }
    ccws->cmd_code = ccw_transfer_in_channel1;
    (u32) ccws->cda = (u32) virt_to_phys (origccws);
}

/*
 * linked list used to hold all the lcs_drvr_globals so they can be
 * freed up when the driver is finished 
 */
static spinlock_t lcs_card_list_lock = SPIN_LOCK_UNLOCKED;
static lcs_drvr_globals *lcs_card_list = NULL;

/*
  This function is required for kernel threads & timers
  going off after the driver is closed & long gone & causing mischef.
  ( thanks Ingo for making me aware that this was happening :-) )
 */
static int
lcs_drvr_globals_valid (lcs_drvr_globals * drvr_globals)
{
    int in_list = FALSE;

    if (drvr_globals) {
	spin_lock (&lcs_card_list_lock);
	in_list = is_in_list ((list *) lcs_card_list, (list *) drvr_globals);
	if (in_list)
	    atomic_inc (&drvr_globals->usage_cnt);
	spin_unlock (&lcs_card_list_lock);
    }
    return (in_list);
}

static void *
lcs_dma_alloc (size_t allocsize)
{
    void *newalloc = kmalloc (allocsize, GFP_KERNEL | GFP_DMA);
    if (newalloc)
	memset (newalloc, 0, allocsize);
    return (newalloc);
}

static void
lcs_usage_free_drvr_globals (lcs_drvr_globals * drvr_globals)
{
#if LCS_ZERO_COPY_READ
    struct sk_buff **skb_buff;
#endif
    u8 **iobuff;
    int cnt, refcnt;

    lcs_debug_event (2, "lcs_usage_free_drvr_globals_called %p\n",
		     drvr_globals);
    if (drvr_globals) {
	lcs_read_globals *read = drvr_globals->read;
	refcnt = atomic_dec_return (&drvr_globals->usage_cnt);
	if (refcnt < 0)
	    lcs_bad_news ("bad ref cnt drvr_globals %p ref %d\n", drvr_globals,
			  refcnt);
	if (refcnt <= 0) {
	    spin_lock (&lcs_card_list_lock);
	    if (!remove_from_list
		((list **) & lcs_card_list, (list *) drvr_globals))
		lcs_bad_news ("drvr globals not in list %p\n", drvr_globals);
	    spin_unlock (&lcs_card_list_lock);
	    lcs_debug_event (2, "freeing drvr globals\n");
	    iobuff = drvr_globals->write->iobuff;
	    for (cnt = 0; cnt < LCS_NUM_TX_BUFFS; cnt++) {
		if (iobuff[cnt])
		    kfree (iobuff[cnt]);
	    }
#if LCS_ZERO_COPY_READ
	    skb_buff = read->skb_buff;
	    for (cnt = 0; cnt < LCS_NUM_RX_BUFFS; cnt++) {
		clear_normalized_cda (&read->ccw[cnt]);
		if (skb_buff[cnt])
		    dev_kfree_skb (skb_buff[cnt]);
	    }
#else
	    iobuff = read->iobuff;
	    for (cnt = 0; cnt < LCS_NUM_RX_BUFFS; cnt++) {
		if (iobuff[cnt])
		    kfree (iobuff[cnt]);
	    }
#endif
	    if (drvr_globals->dev)
		drvr_globals->dev->priv = NULL;
#ifdef  CONFIG_IP_MULTICAST
	    while (drvr_globals->ipm_list) {
		spin_lock (&drvr_globals->ipm_lock);
		remove_from_list ((list **) & drvr_globals->ipm_list,
				  (list *) drvr_globals->ipm_list);
		spin_unlock (&drvr_globals->ipm_lock);
		kfree (drvr_globals->ipm_list);
	    }
#endif
	    memset (drvr_globals, 0, LCS_ALLOCSIZE);
	    kfree (drvr_globals);
	}
    }
}

static void
lcs_kernel_thread (lcs_drvr_globals * drvr_globals)
{
#if LINUX_VERSION_CODE<=KERNEL_VERSION(2,2,16)
    /* tq_scheduler sometimes leaves interrupts disabled from do bottom half */
    __sti ();
#endif
    if (kernel_thread ((int (*)(void *)) drvr_globals->kernel_thread_routine,
		       (void *) drvr_globals, SIGCHLD) < 0) {
	atomic_set (&drvr_globals->kernel_thread_lock, 1);
	lcs_debug_exception (1,
			     "lcs_kernel_thread failed to launch a new thread "
			     "drvr_globals=%p routine=%p",
			     drvr_globals, drvr_globals->kernel_thread_routine);
    }
}

/* 
 * Allocates driver instance globals 
 */
static lcs_drvr_globals *
lcs_alloc_drvr_globals (void)
{
    lcs_drvr_globals *drvr_globals;
    lcs_write_globals *write;
    lcs_read_globals *read;
    int cnt;
    struct tq_struct *lcs_tq;

    lcs_debug_event (2, "lcs_alloc_drvr_globals called\n");
    drvr_globals = lcs_dma_alloc (LCS_ALLOCSIZE);
    if (!drvr_globals)
	return (NULL);
    atomic_set (&drvr_globals->usage_cnt, 1);
    drvr_globals->rel_adapter_no = lcs_invalid_adapter_no;
    read = drvr_globals->read = (lcs_read_globals *) (drvr_globals + 1);
    write = drvr_globals->write =
	(lcs_write_globals *) ((addr_t) read + sizeof (lcs_read_globals));
    write->lock_owner = drvr_globals->read->lock_owner = LCS_INVALID_LOCK_OWNER;
    for (cnt = 0; cnt < LCS_NUM_TX_BUFFS; cnt++) {
	if ((write->iobuff[cnt] = lcs_dma_alloc (LCS_IOBUFFSIZE)) == NULL)
	    goto Fail;

    }
    for (cnt = 0; cnt < LCS_NUM_RX_BUFFS; cnt++) {
#if LCS_ZERO_COPY_READ
	if ((read->skb_buff[cnt] =
	     lcs_dev_alloc_skb (LCS_READ_ALLOCSIZE)) == NULL)
#else
	if ((read->iobuff[cnt] =
	     kmalloc (LCS_READ_ALLOCSIZE, GFP_ATOMIC | GFP_DMA)) == NULL)
#endif
	    goto Fail;
    }
    drvr_globals->read->drvr_globals = drvr_globals;
    write->drvr_globals = drvr_globals;
    lcs_resetstate (drvr_globals);
    lcs_tq = &drvr_globals->kernel_thread_task;
    lcs_tq->routine = (void (*)(void *)) lcs_kernel_thread;
    lcs_tq->data = (void *) drvr_globals;
#if LINUX_VERSION_CODE>KERNEL_VERSION(2,3,0)
    lcs_tq->sync = read->retry_task.sync = write->retry_task.sync = 0;
#endif
#if LINUX_VERSION_CODE>=KERNEL_VERSION(2,3,0)
    INIT_LIST_HEAD (&lcs_tq->list);
    INIT_LIST_HEAD (&read->retry_task.list);
    INIT_LIST_HEAD (&write->retry_task.list);
#endif
    read->retry_task.routine = (void (*)(void *)) lcs_queued_restartreadio;
    write->retry_task.routine = (void (*)(void *)) lcs_queued_restartwriteio;
    write->retry_task.data = read->retry_task.data = (void *) drvr_globals;
    write->resume_task.routine = (void (*)(void *)) lcs_resume_writetask;
    write->resume_task.data = (void *) write;
    read->chan_busy_state = write->chan_busy_state = chan_dead;
    atomic_set (&drvr_globals->kernel_thread_lock, 1);
    spin_lock (&lcs_card_list_lock);
    add_to_list ((list **) & lcs_card_list, (list *) drvr_globals);
    spin_unlock (&lcs_card_list_lock);
    drvr_globals->up = FALSE;
    return (drvr_globals);
Fail:
    lcs_usage_free_drvr_globals (drvr_globals);
    return (NULL);
}

void
lcs_retry (lcs_chan_globals * chan_globals)
{
    lcs_debug_event (1, "lcs_retry subchannel %04x scheduling retry\n",
		     chan_globals->subchannel);
    MOD_INC_USE_COUNT;
#if LINUX_VERSION_CODE>KERNEL_VERSION(2,2,16)
    schedule_task (&chan_globals->retry_task);
#else
    queue_task (&chan_globals->retry_task, &tq_scheduler);
#endif
}

static int
lcs_doio_func (lcs_chan_globals * chan_globals, unsigned long flags)
{
    int rc;
    lcs_debug_event (2, "lcs_doio subchannel=%x\n",
		     (int) chan_globals->subchannel);
    lcs_chan_lock (chan_globals);
    rc = do_IO (chan_globals->subchannel, &chan_globals->ccw[0],
		(addr_t) chan_globals, 0, flags);

    if (rc) {
#if LCS_DEBUG
	lcs_debug_event (1, "lcs_doio do_IO returned %d for subchannel %X\n",
			 rc, chan_globals->subchannel);
#endif
	if (rc != -ENODEV)
	    lcs_retry (chan_globals);
    }
    lcs_chan_unlock (chan_globals);
    return (rc);
}

#define lcs_doio(chan_globals,flags)  \
	lcs_doio_func((lcs_chan_globals *)(chan_globals),(flags))

/* 
 * halt subchannel wrapper used during driver startup 
 */
static int
lcs_hsch_func (lcs_chan_globals * chan_globals)
{
    int rc = 0;
    lcs_debug_event (2, "lcs_hsch subchannel=%x\n",
		     (int) chan_globals->subchannel);
    lcs_chan_lock (chan_globals);
    lcs_initqueue (chan_globals);
    chan_globals->drvr_globals->state = lcs_halting_subchannels;
    rc = halt_IO (chan_globals->subchannel, (addr_t) chan_globals, 0);
    lcs_chan_unlock (chan_globals);
    if (rc == 0)
	rc = lcs_sleepon_func (chan_globals, 0);
    else
	lcs_debug_event (2, "lcs_hsch subchannel=%x rc=%d\n",
			 (int) chan_globals->subchannel, rc);
    return (rc);
}

#define lcs_hsch(chan_globals) \
	lcs_hsch_func((lcs_chan_globals *)(chan_globals))

static void
wait_on_write (lcs_drvr_globals * drvr_globals)
{
    lcs_write_globals *write = drvr_globals->write;

    if (drvr_globals->state == lcs_doing_io) {
	if (drvr_globals->dev)
	    netif_stop_queue (drvr_globals->dev);
	lcs_chan_lock (write);
	if (drvr_globals->state == lcs_doing_io &&
	    (write->chan_busy_state != chan_idle || write->resume_queued)) {
	    lcs_initqueue (write);
	    drvr_globals->state = lcs_requesting_channels_for_lancmds;
	    lcs_chan_unlock (write);
	    lcs_sleepon (write, 0);
	} else
	    lcs_chan_unlock (write);
    }
    drvr_globals->state = lcs_doing_lancmds;
}

static int
lcs_getcmdsize (u8 cmd_code)
{
    int cmdsize;
    switch (cmd_code) {
    case lcs_startlan:
    case lcs_stoplan:
    case lcs_lanstat:
    case lcs_shutdown:
	cmdsize = sizeof (lcs_std_cmd);
	break;
    case lcs_startup:
	cmdsize = sizeof (lcs_startup_cmd);
	break;
#ifdef CONFIG_IP_MULTICAST
    case lcs_setipm:
    case lcs_delipm:
	cmdsize = sizeof (lcs_ipm_ctlmsg);
	break;
    case lcs_qipassist:
	cmdsize = sizeof (lcs_qipassist_ctlmsg);
	break;
#endif
    default:
	lcs_debug_event (1, "illegal lan cmd %d in lcs_getcmdsize\n", cmd_code);
	cmdsize = -EINVAL;
    }
    if (cmdsize != -EINVAL)
	cmdsize -= sizeof (lcs_header_type);
    return (cmdsize);
}

lcs_header_type *
lcs_getfreetxbuff (lcs_write_globals * write, u8 type, u8 slot, int len)
{
    ccw1_t *curr_ccw;
    int new_buff = FALSE;
    lcs_header_type *retbuff;
    u32 virt_cda;

    if (write->prepare_idx == write->tx_idx)
	return (NULL);
    curr_ccw = &write->ccw[write->prepare_idx];
    if (curr_ccw->count == 0)
	new_buff = TRUE;
    else if ((curr_ccw->count + len + sizeof (lcs_header_type) + sizeof (u16)) >
	     LCS_IOBUFFSIZE) {
	curr_ccw->flags &= ~CCW_FLAG_SUSPEND;
	write->prepare_idx =
	    ((write->prepare_idx + 1) & (LCS_NUM_TX_BUFFS - 1));
	if (write->prepare_idx == write->tx_idx)
	    return (NULL);
	new_buff = TRUE;
	curr_ccw = &write->ccw[write->prepare_idx];
    }
    if (new_buff) {
	write->buffsfilled++;
	if ((curr_ccw->flags & CCW_FLAG_SUSPEND) == 0) {
	    lcs_bad_news
		("Bug/Race condition detected in lcs_getfreetxbuff "
		 "CCW_FLAG_SUSPEND not set\n"
		 "for ccw at %p", curr_ccw);
	    return (NULL);
	}
	if (curr_ccw->count != 0) {
	    lcs_bad_news
		("Bug/Race condition detected in lcs_getfreetxbuff count!=0 "
		 "in new buffer\n"
		 "for ccw at %p", curr_ccw);
	    return (NULL);
	}
    }
    len += sizeof (lcs_header_type);
    virt_cda = (u32) virt_to_phys ((void *) ((addr_t) curr_ccw->cda));
    if (new_buff) {
	retbuff = (lcs_header_type *) ((addr_t) virt_cda);
	curr_ccw->count = len + sizeof (u16);
    } else {
	retbuff =
	    (lcs_header_type *) (virt_cda + curr_ccw->count - sizeof (u16));
	curr_ccw->count += len;
	len = curr_ccw->count - sizeof (u16);
    }
    retbuff->offset = len;
    *((u16 *) ((addr_t) (virt_cda + len))) = 0;
    retbuff->type = type;
    retbuff->slot = slot;
    return (retbuff);
}

lcs_std_cmd *
lcs_prepare_lancmd (lcs_write_globals * write, u8 cmd_code, u8 rel_adapter_no,
		    u8 initiator, u8 lan_type)
{
    lcs_std_cmd *writecmd;
    int cmdsize = lcs_getcmdsize (cmd_code);
    if (cmdsize < 0)
	return (NULL);
    /* Force a fresh ccw buff */
    writecmd =
	(lcs_std_cmd *) lcs_getfreetxbuff (write, lcs_control_frame_type, 0,
					   cmdsize);
    if (writecmd == NULL)
	return (NULL);
    memset ((void *) (((addr_t) writecmd) + sizeof (lcs_header_type)), 0,
	    cmdsize);
    writecmd->cmd_code = cmd_code;
    writecmd->initiator = initiator;
    switch (cmd_code) {
    case lcs_startlan:
    case lcs_stoplan:
    case lcs_lanstat:
#ifdef CONFIG_IP_MULTICAST
    case lcs_setipm:
    case lcs_delipm:
    case lcs_qipassist:
#endif
	writecmd->lan_type = lan_type;
	writecmd->rel_adapter_no = rel_adapter_no;
	break;
    case lcs_startup:
	((lcs_startup_cmd *) writecmd)->buff_size = LCS_IOBUFFSIZE;
	break;
    case lcs_shutdown:
	break;
    default:
    }
    return (writecmd);
}

#if LCS_DEBUG_RESUME
int lcs_read_resume_cnt, lcs_write_resume_cnt;
#endif

void
lcs_interrupt_resume_read (lcs_read_globals * read)
{
    int rc;
    ccw1_t *curr_ccw;

    lcs_debug_event (2, "lcs_interrupt_resume_read entered\n");
    if ((read->ccw[(read->rx_idx - 1) & (LCS_NUM_RX_BUFFS - 1)].
	 flags & CCW_FLAG_SUSPEND) == 0)
	lcs_bad_news("lcs_interrupt_resume_read detected possible "
		     "lack of suspend bug\n");
    curr_ccw = &read->ccw[read->rx_idx];
    curr_ccw->flags = CCW_FLAG_CC | CCW_FLAG_SLI | CCW_FLAG_PCI
#if LCS_USE_IDALS
	| (curr_ccw->flags & CCW_FLAG_IDA)
#endif
	;
    if ((rc = resume_IO (read->subchannel)) == 0) {
#if LCS_DEBUG_RESUME
	lcs_read_resume_cnt++;
#endif
	read->chan_busy_state = chan_busy;
    } else
	lcs_bad_news
	    ("lcs_interrupt_resume_read returned %d for subchannel %X\n", rc,
	     read->subchannel);
}

void
lcs_resume_read (lcs_read_globals * read)
{
    lcs_debug_event (2, "lcs_resume_read entered\n");
    lcs_chan_lock (read);
    if (read->chan_busy_state == chan_idle)
	lcs_interrupt_resume_read (read);
    read->lancmd_reply_ptr = NULL;
    lcs_chan_unlock (read);
}

int
lcs_resume_write (lcs_write_globals * write)
{
    int rc = 0;
    ccw1_t *tx_ccw;

    lcs_debug_event (2, "lcs_resume_write entered\n");
    lcs_chan_lock (write);
    tx_ccw = &write->ccw[write->prepare_idx];
    if (write->chan_busy_state == chan_idle) {
	if (tx_ccw->flags & CCW_FLAG_SUSPEND) {
	    tx_ccw->flags &= ~CCW_FLAG_SUSPEND;
	    write->prepare_idx =
		((write->prepare_idx + 1) & (LCS_NUM_TX_BUFFS - 1));
	}
	write->buffsfilled = 0;
	if ((rc = resume_IO (write->subchannel)) == 0) {
#if LCS_DEBUG_RESUME
	    lcs_write_resume_cnt++;
#endif
	    write->chan_busy_state = chan_busy;
	    /* the << TOD_SHIFT_TO_USECS is to adjust the inequality
	       to microseconds for the tod in lcs_tx_is_busy
	       The extra is adjustment is
	       so that the tx_is_busy will weigh 
	       in favour of large transmission buffers.
	     */

	    write->adjusted_last_bytes_still_being_txed =
		(uint64_t) ((write->bytes_still_being_txed +
			     500) << TOD_SHIFT_TO_USECS);
	    write->last_resume_time = lcs_get_tod ();
	} else
	    lcs_bad_news ("lcs_resume_write returned %d for subchannel %X\n",
			  rc, write->subchannel);

    }
    lcs_chan_unlock (write);
    return rc;
}

/*
  Unfortunately we don't know if the lcs interface is busy
  simply by knowing we have started a transmission & not
  recieved a interrupt yet because the osa microcode simply
  copies the packet into its own internal buffers.
  I estimate whether the interface maybe busy by assuming
  an extremely optimistic 30MB/sec transmission rate ( might
  happen on a gigabit ethernet card ), i.e. if this routine
  returns true the interface is almost definitely busy.
 */
static int
lcs_tx_is_busy (lcs_write_globals * write, uint64_t tod)
{
    uint64_t delta_tx = tod - write->last_resume_time;
    int rc;

    /* 30MB/sec = 30 bytes per usec */
    if (write->adjusted_last_bytes_still_being_txed < (30 * delta_tx))
	rc = FALSE;
    else
	rc = TRUE;
#if 0
    lcs_debug_event (2,
		     "%s being txed %08X tod %08X last resume=%08X "
		     "delta_tx %08X\n",
		     (rc ? "lcs busy" : "lcs not busy"),
		     (unsigned) ((write->
				  adjusted_last_bytes_still_being_txed >>
				  TOD_SHIFT_TO_USECS)),
		     (unsigned) (tod >> TOD_SHIFT_TO_USECS),
		     (unsigned) (write->last_resume_time >> TOD_SHIFT_TO_USECS),
		     (unsigned) ((delta_tx) >> TOD_SHIFT_TO_USECS));
#endif
    return (rc);
}

static void
lcs_queue_write (lcs_write_globals * write)
{
    /* wait on write in shutdown func should avoid this needing mod
     * usage counts */
    write->resume_queued = TRUE;
    queue_task (&write->resume_task, &tq_immediate);
    mark_bh (IMMEDIATE_BH);
}

static void
lcs_queue_write_if_necessary (lcs_write_globals * write)
{
    if (!write->resume_queued) {
	write->resume_loopcnt = 0;
	lcs_queue_write (write);
    }
}

int
lcs_should_queue_write (lcs_write_globals * write, uint64_t tod)
{
    if ((write->buffsfilled < (LCS_NUM_TX_BUFFS - 1))
	&& (lcs_tx_is_busy (write, tod) ||
	    ((tod - write->last_lcs_txpacket_time) <
	     (25 << TOD_SHIFT_TO_USECS))))
	return (TRUE);
    return (FALSE);

}

static void
lcs_resume_writetask (lcs_write_globals * write)
{
    uint64_t tod;

    if (write->buffsfilled) {
	tod = lcs_get_tod ();
	if (write->resume_loopcnt < 20 && lcs_should_queue_write(write,tod)) {
	    write->resume_loopcnt++;
	    lcs_queue_write (write);
	    return;
	} else {
#if 0
	    if (write->resume_loopcnt > 10)
		lcs_debug_event (1, "lcs_resume_writetask bad loopcnt %d\n",
				 write->resume_loopcnt);
#endif
	    lcs_resume_write (write);
	}

    }
    write->resume_queued = FALSE;

}

int
lcs_calcsleeptime (lcs_drvr_globals * drvr_globals, u8 cmd_code)
{
    if ((cmd_code == lcs_startlan || cmd_code == lcs_stoplan))
	return (drvr_globals->slow_hw ? 60 : 5);
    else
	return (drvr_globals->slow_hw ? 5 : 0);

}

int
lcs_resumeandwait (lcs_write_globals * write, u8 cmd_code)
{
    int rc;

    lcs_initqueue (write);
    if ((rc = lcs_resume_write (write)))
	return (rc);
    return (lcs_sleepon
	    (write, lcs_calcsleeptime (write->drvr_globals, cmd_code)));
}

static int
lcs_lgw_common (lcs_drvr_globals * drvr_globals, u8 cmd_code)
{

    lcs_write_globals *write = drvr_globals->write;
    lcs_std_cmd *writecmd;
    wait_on_write (drvr_globals);
    writecmd = lcs_prepare_lancmd (write, cmd_code,
				   drvr_globals->rel_adapter_no,
				   lcs_lgw_initiated, drvr_globals->lan_type);
    writecmd->sequence_no = drvr_globals->lgw_sequence_no;
    return (lcs_resumeandwait (write, cmd_code));
}

static void
lcs_start_doing_io (lcs_drvr_globals * drvr_globals)
{
    netif_start_queue (drvr_globals->dev);
    drvr_globals->state = lcs_doing_io;
}

static int
lcs_lgw_stoplan (lcs_drvr_globals * drvr_globals)
{
    struct net_device *dev;

    lcs_daemonize ("lcs_lgw_stoplan", 0, TRUE);
    if (!lcs_drvr_globals_valid (drvr_globals))
	goto Done2;
    if (drvr_globals->state != lcs_doing_io)
	goto Done1;
    dev = drvr_globals->dev;
    lcs_bad_news ("lcs problems detected %s temporarily unavailable.\n",
		  dev->name);
    lcs_lgw_common (drvr_globals, lcs_stoplan);
    netif_stop_queue (dev);
    drvr_globals->state = lcs_doing_io;
Done1:
    atomic_set (&drvr_globals->kernel_thread_lock, 1);
    lcs_usage_free_drvr_globals (drvr_globals);
Done2:
    MOD_DEC_USE_COUNT;
    return (0);
}

static int
lcs_lgw_startlan (lcs_drvr_globals * drvr_globals)
{
    struct net_device *dev;

    lcs_daemonize ("lcs_lgw_startlan", 0, TRUE);
    if (!lcs_drvr_globals_valid (drvr_globals))
	goto Done2;
    if (drvr_globals->state != lcs_doing_io)
	goto Done1;
    dev = drvr_globals->dev;
    lcs_debug_event (0, "lcs device %s restarted.\n", dev->name);
    lcs_lgw_common (drvr_globals, lcs_startlan);
    lcs_start_doing_io (drvr_globals);
Done1:
    atomic_set (&drvr_globals->kernel_thread_lock, 1);
    lcs_usage_free_drvr_globals (drvr_globals);
Done2:
    MOD_DEC_USE_COUNT;
    return (0);
}

static int
lcs_lgw_resetlan (lcs_drvr_globals * drvr_globals)
{
    struct net_device *dev;
    int retrycnt;

    lcs_daemonize ("lcs_lgw_resetlan", 0, TRUE);
    if (!lcs_drvr_globals_valid (drvr_globals))
	goto Done2;
    if (drvr_globals->state != lcs_doing_io)
	goto Done1;
    dev = drvr_globals->dev;
    lcs_debug_event (0, "lcs reset %s.\n", dev->name);
    for (retrycnt = 0; retrycnt < 10; retrycnt++) {
	if (lcs_detect (drvr_globals, drvr_globals->rel_adapter_no, 0, 0,
			drvr_globals->lan_type,
			dev->dev_addr, drvr_globals->reset_retry_cnt) == 0) {
	    lcs_start_doing_io (drvr_globals);
	    lcs_good_news ("lcs reset %s successfully.\n", dev->name);
	    goto Done1;
	}
	lcs_debug_event (0, "lcs_resetlan retrycnt %d\n", retrycnt);
	schedule_timeout (3 * HZ);
	drvr_globals->reset_retry_cnt = 0;
    }
    lcs_resetstate (drvr_globals);
    lcs_bad_news ("lcs problems occured restarting after reset %s.\n",
		  dev->name);
Done1:
    atomic_set (&drvr_globals->kernel_thread_lock, 1);
    lcs_usage_free_drvr_globals (drvr_globals);
Done2:
    MOD_DEC_USE_COUNT;
    return (0);
}

static void
lcs_queue_thread (int (*routine) (lcs_drvr_globals *),
		  lcs_drvr_globals * drvr_globals)
{
    if (atomic_dec_and_test (&drvr_globals->kernel_thread_lock)) {
	netif_stop_queue (drvr_globals->dev);
	MOD_INC_USE_COUNT;
	drvr_globals->kernel_thread_routine = routine;
#if LINUX_VERSION_CODE<=KERNEL_VERSION(2,2,16)
	queue_task (&drvr_globals->kernel_thread_task, &tq_scheduler);
#else
	schedule_task (&drvr_globals->kernel_thread_task);
#endif
    } else
	lcs_debug_event (1, "lcs_queue_thread busy drvr_globals=%p routine=%p",
			 drvr_globals, routine);
}

static int
lcs_check_reset (lcs_chan_globals * chan_globals)
{
    lcs_drvr_globals *drvr_globals = chan_globals->drvr_globals;
    devstat_t *devstat = &chan_globals->devstat;

    if (devstat->flag & DEVSTAT_FLAG_SENSE_AVAIL) {
	if (drvr_globals->state == lcs_doing_io &&
	    (devstat->scnt >= 1) &&
	    ((lcs_sense_byte_1) (devstat->ii.sense.data[1]))
	    == resetting_event) {
	    lcs_debug_exception (0, "lcs reseting event occuring devno=%04x\n",
				 (int) chan_globals->devno);
	    drvr_globals->reset_retry_cnt = 0;
	    switch (drvr_globals->state) {
	    case lcs_idle_requesting_channels_for_lancmds:
	    case lcs_requesting_channels_for_lancmds:
	    case lcs_got_write_channel_for_lancmds:
	    case lcs_doing_lancmds:
		lcs_restartreadio (drvr_globals);
		lcs_restartwriteio (drvr_globals);
		break;
	    case lcs_doing_io:
		lcs_queue_thread (lcs_lgw_resetlan, drvr_globals);
		break;
	    default:
	    }
	    return (TRUE);
	} else {
	    if ((devstat->scnt >= 1) && ((lcs_sense_byte_0)
					 (devstat->ii.sense.data[0]) ==
					 (intervention_required |
					  interface_disconnect)))
		lcs_debug_exception (0,
				     "lcs intervention_required "
				     "& interface_disconnect detected "
				     "devno=0x%04x\n",
				     (int) chan_globals->devno);
	    else {
		lcs_debug_event (0, "lcs unknown sense data devno=0x%04x\n"
				 "sense_cnt=%d sense_data=\n"
				 "%08x %08x %08x %08x\n"
				 "%08x %08x %08x %08x\n",
				 (int) chan_globals->devno,
				 (int) devstat->scnt,
				 ((int *) &devstat->ii.sense.data)[0],
				 ((int *) &devstat->ii.sense.data)[1],
				 ((int *) &devstat->ii.sense.data)[2],
				 ((int *) &devstat->ii.sense.data)[3],
				 ((int *) &devstat->ii.sense.data)[4],
				 ((int *) &devstat->ii.sense.data)[5],
				 ((int *) &devstat->ii.sense.data)[6],
				 ((int *) &devstat->ii.sense.data)[7]);
		if (devstat->scnt > 2 ||
		    (devstat->ii.sense.data[0] == 0
		     && devstat->ii.sense.data[1] == 0)) {
		    lcs_debug_event (0,"lcs sense data looks like "
				     "rubbish ignoring it\n");
		    devstat->flag &= ~DEVSTAT_FLAG_SENSE_AVAIL;
		}
	    }
	}
    }
    return (FALSE);
}

#if LCS_ZERO_COPY_READ
static inline void
lcs_rxskb (lcs_drvr_globals * drvr_globals, struct sk_buff *skb, u16 pkt_len,
	   u16 offset, struct net_device_stats *stats)
{
    stats->rx_bytes += pkt_len;
    if (use_hw_stats == FALSE)
	stats->rx_packets++;
    lcs_debug_event (2, "netif_rx called\n");
    lcs_debug_event (2, "received %u bytes rx_bytes=%lu\n",
		     (unsigned) pkt_len, stats->rx_bytes);
    skb_reserve (skb, sizeof (lcs_header_type) + offset);
    skb_put (skb, pkt_len);
    /* Horrible horrible kludge to fix bug in some socket code */
    /* usage of skb->truesize hopefully this bug will be fixed in */
    /* upper layers soon */
    skb->truesize = skb->len;
    skb->protocol = drvr_globals->lan_type_trans (skb, skb->dev);
    netif_rx (skb);
}
#endif

void
lcs_rxpacket (lcs_drvr_globals * drvr_globals, u8 * start_lcs_buff
#if LCS_ZERO_COPY_READ
	      , u32 curr_idx
#endif
    )
{
    lcs_read_globals *read = NULL;

    struct sk_buff *curr_skb
#if LCS_ZERO_COPY_READ
           , *prev_skb
#endif
           ;
    struct net_device *dev = NULL;
    struct net_device_stats *stats = NULL;
    lcs_header_type *lcs_header_ptr = (lcs_header_type *) start_lcs_buff;
    u16 curr_offset, curr_pkt_len
#if  LCS_ZERO_COPY_READ
       , prev_pkt_len, curr_skb_offset, prev_skb_offset
#endif
       ;

    lcs_debug_event (2, "Entering lcs_rxpacket\n");
    if (drvr_globals == NULL) {
	lcs_rxerror ("drvr_globals=NULL\n", drvr_globals);
	return;
    }

    dev = drvr_globals->dev;
    read = drvr_globals->read;
#if LCS_ZERO_COPY_READ
    if (atomic_read (skb_datarefp (read->skb_buff[curr_idx])) != 1) {
	lcs_bad_news ("lcs_rxpacket skb->refcnt=%d start_lcs_buff=%p\n",
		      atomic_read (skb_datarefp (read->skb_buff[curr_idx])),
		      start_lcs_buff);
	return;
    }
#endif
    stats = &drvr_globals->stats;
    lcs_debug_event (2, "We appear to have received a packet buff=%p\n",
		     lcs_header_ptr);
#if LCS_ZERO_COPY_READ
    curr_skb = NULL;
    curr_pkt_len = curr_skb_offset =
#endif
	curr_offset = 0;
    while (lcs_header_ptr->offset) {
	lcs_debug_event(3,"lcs_header_ptr=%p curroffset=%d\n", lcs_header_ptr,
			curr_offset);
#if LCS_ZERO_COPY_READ
	prev_pkt_len = curr_pkt_len;
	prev_skb = curr_skb;
	prev_skb_offset = curr_skb_offset;
#endif
#if 0
	static int idx = 0;
	idx++;
	if ((idx & 0x1ff) == 0) {
	    lcs_header_ptr->type = lcs_control_frame_type;
	    ((lcs_std_cmd *) lcs_header_ptr)->initiator = lcs_lgw_initiated;
	    (((lcs_std_cmd *) lcs_header_ptr)->cmd_code) = lcs_startup;
	}
#endif
	if (lcs_header_ptr->offset > LCS_IOBUFFSIZE ||
	    lcs_header_ptr->offset < curr_offset) {
	    if (use_hw_stats == FALSE)
		stats->rx_length_errors++;
	    lcs_rxerror ("lcs whacko buffer error\n", drvr_globals);
	    return;
	}

	if (lcs_header_ptr->type == lcs_control_frame_type) {
	    if (((lcs_std_cmd *) lcs_header_ptr)->initiator == lcs_lgw_initiated
		&& drvr_globals->state == lcs_doing_io) {
		drvr_globals->lgw_sequence_no =
		    ((lcs_std_cmd *) lcs_header_ptr)->sequence_no;
		lcs_debug_display_read_buff ("lgw_initiated_command read buff",
					     ((lcs_std_cmd *) lcs_header_ptr));
		switch ((int) (((lcs_std_cmd *) lcs_header_ptr)->cmd_code)) {
		case lcs_startup:
		    drvr_globals->reset_retry_cnt = -1;
		    lcs_queue_thread (lcs_lgw_resetlan, drvr_globals);
		    break;
		case lcs_startlan:
		    lcs_queue_thread (lcs_lgw_startlan, drvr_globals);
		    break;
		case lcs_stoplan:
		    lcs_queue_thread (lcs_lgw_stoplan, drvr_globals);
		    break;
		default:
		    lcs_debug_exception (1, "lcs unrecognised lgw"
					 " command received 0x%02x"
					 " on devno=%04x\n",
					 (int) ((lcs_std_cmd *)
						lcs_header_ptr)
					 ->cmd_code, (int) read->devno);
		}
	    } else {
		if (drvr_globals->state == lcs_doing_lancmds) {
		    if (((((lcs_std_cmd *) lcs_header_ptr)->sequence_no ==
			  drvr_globals->sequence_no)) &&
			(((lcs_std_cmd *) lcs_header_ptr)->cmd_code ==
			 drvr_globals->cmd_code)) {
			read->lancmd_reply_ptr = (lcs_std_cmd *) lcs_header_ptr;

			lcs_debug_event (2, "got a valid lancommand %p "
					 "cmd_code=%02x sequence_no=%d\n",
					 read->lancmd_reply_ptr,
					 (int) ((lcs_std_cmd *)
						lcs_header_ptr)->cmd_code,
					 (int) ((lcs_std_cmd *)
						lcs_header_ptr)->sequence_no);
			lcs_wakeup ((lcs_chan_globals *) read, 0);
			break;
		    }
		}
	    }
	} else if (dev && drvr_globals->state == lcs_doing_io) {	
	    /* the packet is for tcpip */

	    /* dev_alloc_skb allocs with GFP_ATOMIC currently */
	    /* so this should be okay under interrupt */
	    /* hopefully .... */
#if LCS_ZERO_COPY_READ
	    if (curr_skb == NULL) {
		/* We need to detach the buffer */
		struct sk_buff *new_skb;
		if ((new_skb=lcs_dev_alloc_skb(LCS_READ_ALLOCSIZE)) == NULL) {
		    stats->rx_dropped++;
		    lcs_bad_news ("%s: Memory squeeze, dropping packet.\n",
				  dev->name);
		    return;
		}

		curr_skb = read->skb_buff[curr_idx];
		curr_skb->dev = dev;
		curr_skb->ip_summed = (checksum_received_ip_pkts
				       ? CHECKSUM_NONE : CHECKSUM_UNNECESSARY);
		clear_normalized_cda (&read->ccw[curr_idx]);
		read->skb_buff[curr_idx] = new_skb;
		set_normalized_cda (&read->ccw[curr_idx],
				    virt_to_phys ((void *) ((addr_t) new_skb->
							    data)));
	    } else {
		curr_skb = skb_clone (prev_skb, GFP_ATOMIC);
		if (curr_skb == NULL) {
		    curr_skb = prev_skb;
		    stats->rx_dropped++;
		    lcs_bad_news ("%s: Memory squeeze, dropping packet.\n",
				  dev->name);
		    break;
		}
		curr_skb_offset = curr_offset;
	    }
	    if (prev_skb)
		lcs_rxskb (drvr_globals, prev_skb, prev_pkt_len,
			   prev_skb_offset, stats);
	    curr_pkt_len =
		lcs_header_ptr->offset - curr_skb_offset -
		sizeof (lcs_header_type);
#else
	    curr_pkt_len =
		lcs_header_ptr->offset - curr_offset - sizeof (lcs_header_type);
	    curr_skb = lcs_dev_alloc_skb (curr_pkt_len);
	    if (curr_skb) {
		curr_skb->dev = dev;
		memcpy (skb_put (curr_skb, curr_pkt_len), lcs_header_ptr + 1,
			curr_pkt_len);
		curr_skb->protocol =
		    drvr_globals->lan_type_trans (curr_skb, dev);
		curr_skb->ip_summed =
		    (checksum_received_ip_pkts ? CHECKSUM_NONE :
		     CHECKSUM_UNNECESSARY);
		stats->rx_bytes += curr_pkt_len;
		if (use_hw_stats == FALSE)
		    stats->rx_packets++;
		lcs_debug_event (2, "netif_rx called\n");
		lcs_debug_event (2, "received %u bytes rx_bytes=%lu\n",
				 (unsigned) curr_pkt_len, stats->rx_bytes);
		netif_rx (curr_skb);
	    } else {
		lcs_bad_news ("%s: Memory squeeze, dropping packet.\n",
			      dev->name);
		drvr_globals->stats.rx_dropped++;
	    }
#endif
	}
	curr_offset = lcs_header_ptr->offset;
	((addr_t) lcs_header_ptr) = start_lcs_buff + curr_offset;
    }
#if LCS_ZERO_COPY_READ
    if (curr_skb)
	lcs_rxskb (drvr_globals, curr_skb, curr_pkt_len, curr_skb_offset,
		   stats);
#endif
    return;
}

/*
 * This is the main interrupt handler it does a lot of sanity checking &
 * wakes up a process if doing startup or shutdown or calls 
 * queues lcs_rxpacket if a network packet is received or
 * resets transmit busy a network packet has been transmitted &
 * marks the bottom half so that networking knows it can transmit more packets 
 * N.B. if we decide to call ssch directly or indirectly from the irq_handler 
 * we had better increment chan_globals->lock_cnt & decrement it when done
 * to avoid deadlocks (this count can be over 2 inside the interrupt handler),
 * but not in normal tasks.
 */

void
lcs_handle_irq (int irq, devstat_t * devstat, struct pt_regs *regs)
{
    union {
	lcs_read_globals *read;
	lcs_write_globals *write;
	lcs_chan_globals *globals;
    } chan;
    struct net_device *dev = NULL;
    lcs_drvr_globals *drvr_globals = NULL;
    lcs_debug_initmessage struct net_device_stats *stats;
    ccw1_t *curr_ccw;
    lcs_header_type *lcs_hdr;
    u32 curr_ccwidx, curr_ccwidx2, curr_idx;
    int chan_globals_valid = FALSE;

    chan.globals = NULL;
    if (!devstat)
	goto lcs_int_spurious;
    chan.globals = (lcs_chan_globals *) devstat->intparm;
    if (!chan.globals || (irq != chan.globals->subchannel))
	goto lcs_int_spurious;
    chan_globals_valid = TRUE;
    drvr_globals = chan.globals->drvr_globals;
    lcs_debug_event (2, "lcs_handle_irq irq=%x chan_globals=%p drvr_globals=%p"
		     " drvr_state=%d devstat=%p irb=%p cpu=%d\n",
		     irq, chan.globals, drvr_globals, drvr_globals->state,
		     &chan.globals->devstat,
		     &chan.globals->devstat.ii.irb, smp_processor_id ());
    if (chan.globals->lock_cnt)
	lcs_bad_news ("lcs_handle_irq lock owner %d lock_cnt=%d\n",
		      chan.globals->lock_owner, chan.globals->lock_cnt);
    if (drvr_globals->state == lcs_interrupted) {
	lcs_wakeup (chan.globals, -EINTR);
    } else {
	if (lcs_check_reset (chan.globals))
	    return;
	if (chan.globals->irq_allocated_magic != LCS_MAGIC)
	    goto lcs_int_spurious;
    }
    dev = drvr_globals->dev;
    if (dev == NULL && drvr_globals->state == lcs_doing_io) {
	lcs_debug_setmessage ("dev=NULL in handle irq");
	goto lcs_int_debug_info;
    }
    stats = &drvr_globals->stats;
    if (drvr_globals->state == lcs_halting_subchannels) {
	lcs_wakeup (chan.globals, 0);
	return;
    }
    chan.globals->lock_owner = smp_processor_id ();
    chan.globals->lock_cnt = 1;
    if (drvr_globals->read == chan.read) {
	if (devstat->dstat != 0 || (devstat->cstat & ~SCHN_STAT_PCI) != 0) {

	    lcs_debug_event (0,
			     "lcs_handle_irq dstat=%02x cstat=%02x "
			     "drvr_globals_state=%d\n",
			     devstat->dstat, devstat->cstat,
			     drvr_globals->state);
	    switch (drvr_globals->state) {
	    case lcs_idle_requesting_channels_for_lancmds:
	    case lcs_requesting_channels_for_lancmds:
	    case lcs_got_write_channel_for_lancmds:
	    case lcs_doing_lancmds:
		lcs_restartreadio (drvr_globals);
		break;
	    case lcs_doing_io:
		lcs_retry (chan.globals);
		break;
	    default:
	    }
	    goto lcs_normal_ret;
	}
	if (chan.read->chan_busy_state == chan_starting_up)
	    chan.read->chan_busy_state = chan_idle;
	else {
	    switch (drvr_globals->state) {
	    case lcs_doing_io:
	    case lcs_doing_lancmds:

		curr_idx =
		    ((ccw1_t *) phys_to_virt (devstat->cpa) -
		     &chan.read->ccw[0]);
		/* We don't need to divide by sizeof(ccw1_t) here as we are
		   doing pointer arithmetic */
		curr_ccwidx = ((curr_idx - 2) & (LCS_NUM_RX_BUFFS - 1));
		curr_ccwidx2 = ((curr_idx - 1) & (LCS_NUM_RX_BUFFS - 1));
		for (curr_idx = chan.read->rx_idx;
		     curr_idx != curr_ccwidx2;
		     curr_idx = ((curr_idx + 1) & (LCS_NUM_RX_BUFFS - 1))) {
#if LCS_ZERO_COPY_READ
		    lcs_hdr =
			(lcs_header_type *) (chan.read->skb_buff[curr_idx]->
					     data);
#else
		    lcs_hdr = (lcs_header_type *)
			(chan.read->iobuff[curr_idx]);
#endif

		    if (lcs_hdr->offset != LCS_ILLEGAL_OFFSET) {
			/* Detect duplicate interrupts unfilled buffers etc. */
			lcs_rxpacket (drvr_globals, (u8 *) lcs_hdr
#if LCS_ZERO_COPY_READ
				      , curr_idx
#endif
			    );
			if (chan.read->lancmd_reply_ptr == NULL) {
#if LCS_ZERO_COPY_READ
			    lcs_hdr =
				(lcs_header_type *) (chan.read->
						     skb_buff[curr_idx]->data);
#endif
			    lcs_hdr->offset = LCS_ILLEGAL_OFFSET;
			}
		    } else {
			lcs_bad_news ("lcs_handle_irq race condition Illegal"
				      " offset buffer for ccw=%p\n",
				      &chan.read->ccw[curr_idx]);
			goto lcs_int_debug_info;
		    }
		}
		if (chan.read->rx_idx != curr_ccwidx2) {
		    curr_ccw = &chan.read->ccw[curr_ccwidx];
		    curr_ccw->flags =
			CCW_FLAG_CC | CCW_FLAG_SLI | CCW_FLAG_SUSPEND
#if LCS_USE_IDALS
			| (curr_ccw->flags & CCW_FLAG_IDA)
#endif
			;
		    curr_idx = (chan.read->rx_idx - 1) & (LCS_NUM_RX_BUFFS - 1);
		    curr_ccw = &chan.read->ccw[curr_idx];
		    curr_ccw->flags = CCW_FLAG_CC | CCW_FLAG_SLI | CCW_FLAG_PCI
#if LCS_USE_IDALS
			| (curr_ccw->flags & CCW_FLAG_IDA)
#endif
			;
		    chan.read->rx_idx = curr_ccwidx2;
		}
		break;
	    default:
	    }
	}

	/*
	   We need to suspend if till the lancmd reply is consumed.
	 */
	if (
#if BUG
	       (devstat->flag & DEVSTAT_SUSPENDED)
#else
	       devstat->ii.irb.scsw.actl & SCSW_ACTL_SUSPENDED
#endif
	       && chan.read->lancmd_reply_ptr == NULL) {

	    chan.read->chan_busy_state = chan_idle;
	    lcs_debug_event (2, "resume called from interrupt handler.\n");
	    lcs_interrupt_resume_read (chan.read);
	}
    } else {

	if (devstat->dstat != 0 ||
	    devstat->flag & DEVSTAT_FLAG_SENSE_AVAIL || devstat->cstat != 0) {
	    lcs_debug_event (0,
			     "lcs_handle_irq write channel dstat=%02x flag=%x "
			     "cstat=%02x drvr_globals_state=%d\n",
			     (int) devstat->dstat, (int) devstat->flag,
			     (int) devstat->cstat, drvr_globals->state);
	    switch (drvr_globals->state) {
	    case lcs_idle_requesting_channels_for_lancmds:
	    case lcs_requesting_channels_for_lancmds:
	    case lcs_got_write_channel_for_lancmds:
	    case lcs_doing_lancmds:
		lcs_restartwriteio (drvr_globals);
		break;
	    case lcs_doing_io:
		lcs_queue_thread (lcs_lgw_resetlan, drvr_globals);
		break;
	    default:
	    }
	    goto lcs_normal_ret;
	}
	if (chan.write->chan_busy_state == chan_starting_up)
	    chan.write->chan_busy_state = chan_idle;
	else {
	    chan.write->chan_busy_state = chan_idle;

#if BUG
	    if (devstat->flag & DEVSTAT_SUSPENDED)
#else
	    if (devstat->ii.irb.scsw.actl & SCSW_ACTL_SUSPENDED)
#endif
	    {
		switch (drvr_globals->state) {
		case lcs_doing_io:
		case lcs_requesting_channels_for_lancmds:
		case lcs_doing_lancmds:
		    curr_idx =
			((ccw1_t *) phys_to_virt (devstat->cpa) -
			 &chan.write->ccw[0]);
		    curr_ccwidx = ((curr_idx - 2) & (LCS_NUM_TX_BUFFS - 1));
		    curr_ccwidx2 = ((curr_idx - 1) & (LCS_NUM_TX_BUFFS - 1));
		    if (curr_ccwidx == chan.write->tx_idx) {
			lcs_bad_news
			    ("lcs_handle_irq race tx_idx=curr_ccwidx "
			     "write_globals=%p\n",
			     chan.write);
			goto lcs_int_debug_info;
		    }

		    for (curr_idx =
			 ((chan.write->tx_idx + 1) & (LCS_NUM_TX_BUFFS - 1));
			 curr_idx != curr_ccwidx2;
			 curr_idx = ((curr_idx + 1) & (LCS_NUM_TX_BUFFS - 1))) {
			curr_ccw = &chan.write->ccw[curr_idx];
			curr_ccw->flags |= CCW_FLAG_SUSPEND;
			curr_ccw->count = 0;
		    }
		    chan.write->tx_idx = curr_ccwidx;
		    curr_ccw = &chan.write->ccw[chan.write->prepare_idx];
		    if (curr_ccw != &chan.write->ccw[curr_ccwidx]) {
			if (curr_ccw->count != 0) {
			    /* Tx the buffer currently being prepared */
			    if (drvr_globals->state == lcs_doing_io &&
				lcs_should_queue_write (chan.write,
							lcs_get_tod ()))
				lcs_queue_write_if_necessary (chan.write);
			    else
				lcs_resume_write (chan.write);
			}
		    } else {
			lcs_bad_news("lcs_handle_irq race tx_idx=prepare_idx "
				     "write_globals=%p\n",
			     chan.write);
			goto lcs_normal_ret;
		    }
		    break;
		default:
		}
	    }
	}
	switch (drvr_globals->state) {
	case lcs_idle_requesting_channels_for_lancmds:
	case lcs_requesting_channels_for_lancmds:
	    drvr_globals->state = lcs_got_write_channel_for_lancmds;
	case lcs_doing_lancmds:
	    lcs_wakeup (chan.globals, 0);
	    break;
	case lcs_doing_io:
	    stats->tx_bytes += chan.write->bytes_still_being_txed;
	    chan.write->bytes_still_being_txed = 0;
	    if (!use_hw_stats) {
		stats->tx_packets += chan.write->pkts_still_being_txed;
		chan.write->pkts_still_being_txed = 0;

	    }
	    netif_start_queue (dev);
	    break;
	default:
	}
    }
    goto lcs_normal_ret;
lcs_int_spurious:
    lcs_debug_setmessage ("received spurious interrupt");
lcs_int_debug_info:
    lcs_debugchannel (message, chan.globals);
lcs_normal_ret:
    lcs_debug_event (2, "lcs irq exited irq=%x\n", irq);
    if (chan_globals_valid) {
	chan.globals->lock_owner = LCS_INVALID_LOCK_OWNER;
	chan.globals->lock_cnt = 0;
    }
}

/* 
 * Wrappers to allocate irqs if these fail the io channel is probably
 * being used by another device.
 */
static int
lcs_allocirq_func (lcs_chan_globals * chan_globals)
{
    int rc;

    lcs_debug_event (2, "lcs_allocirq called chan_globals=%p\n", chan_globals);
    if (chan_globals->irq_allocated_magic) {
	lcs_bad_news ("lcs_allocirq irq already allocated");
	return -EBUSY;
    } else {
	chan_globals->devstat.intparm = (addr_t) chan_globals;
	rc =
#if LCS_CHANDEV
	    chandev_request_irq
#else
	    request_irq
#endif
	    (chan_globals->subchannel,
	     (void (*)(int, void *, struct pt_regs *)) lcs_handle_irq,
	     0, cardname, &chan_globals->devstat);
	if (!rc)
	    chan_globals->irq_allocated_magic = LCS_MAGIC;

#if LCS_DEBUG
	else
	    lcs_debug_event (2,"request irq failed error code %d "
			     "subchannel %2X\n",
			     rc, chan_globals->subchannel);
#endif
	return rc;
    }
}

#define lcs_allocirq(chan_globals) \
	lcs_allocirq_func((lcs_chan_globals *)(chan_globals))

static void
lcs_freeirq (lcs_chan_globals * chan_globals)
{
    if (chan_globals->irq_allocated_magic) {
#if LCS_CHANDEV
	chandev_free_irq (chan_globals->subchannel, &chan_globals->devstat);
#else
	free_irq (chan_globals->subchannel, &chan_globals->devstat);
#endif
	chan_globals->irq_allocated_magic = 0;
    }
}

static lcs_inline void
lcs_freeallirqs (lcs_drvr_globals * drvr_globals)
{
    lcs_debug_event (2, "lcs_freeallirqs called %p\n", drvr_globals);
    lcs_freeirq ((lcs_chan_globals *) drvr_globals->read);
    lcs_freeirq ((lcs_chan_globals *) drvr_globals->write);
}

/*
 * Generic routine for sending all startup & shutdown command primatives  
 * to lcs.
 * A unit check with a sense code of 0x42 may be presented here if this
 * happens we restart the startup procedure, currently the osa card has been
 * seen to present the unit check very late in the startup sequence 
 * & resend outstanding buffers already read, the source of this problem
 * is unknown, this problem is handled basically by checking sequence 
 * numbers & disgarding packets already received ).
 */
static int
lcs_lancmd (lcs_cmd cmd_code, u8 rel_adapter_no, lcs_drvr_globals * drvr_globals
#ifdef CONFIG_IP_MULTICAST
	    , u16 num_ipm_pairs, lcs_ip_mac_addr_pair * ip_mac_pairs
#endif
    )
{
    lcs_std_cmd *readcmd, *writecmd;
    lcs_read_globals *read;
    lcs_write_globals *write;
    int rc = 0;
    int retry_cnt = 0;

    read = drvr_globals->read;
    write = drvr_globals->write;
    lcs_debug_exception (2, "lcs_lancmd cmd code=%02x drvr_globals=%p\n",
			 (int) cmd_code, drvr_globals);
    wait_on_write (drvr_globals);
Retry:
    read->lancmd_reply_ptr = NULL;
    do {
	if ((writecmd = lcs_prepare_lancmd (write, cmd_code, rel_adapter_no,
					    lcs_390_initiated,
					    drvr_globals->lan_type)) == 0) {
	    lcs_debug_event (2, "lancmd2 lcs_prepare_cmd failed\n");
	    return (-EIO);
	}
	lcs_debug_event (2, "lcs_lancmd writecmd=%p\n", writecmd);
	read->lancmd_reply_ptr = NULL;
	lcs_initqueue (read);
#ifdef CONFIG_IP_MULTICAST
	if (cmd_code == lcs_setipm || cmd_code == lcs_delipm) {
	    ((lcs_ipm_ctlmsg *) writecmd)->version = 4;
	    ((lcs_ipm_ctlmsg *) writecmd)->num_ipm_pairs = num_ipm_pairs;
	    memcpy (&((lcs_ipm_ctlmsg *) writecmd)->ip_mac_pair[0],
		    ip_mac_pairs,
		    sizeof (lcs_ip_mac_addr_pair) * num_ipm_pairs);
	}
	if (cmd_code == lcs_qipassist) {
	    ((lcs_qipassist_ctlmsg *) writecmd)->num_ip_pairs = 0;
	    ((lcs_qipassist_ctlmsg *) writecmd)->version = 4;
	}
#endif
	writecmd->sequence_no = ++drvr_globals->sequence_no;
	lcs_debug_event (2, "sequence_no=%d\n",
			 (int) drvr_globals->sequence_no);
	drvr_globals->cmd_code = cmd_code;
	rc = lcs_resumeandwait (write, cmd_code);
    } while (rc == -EAGAIN && retry_cnt++ < 3);
    if (rc)
	goto Done;
    rc = lcs_sleepon (read, lcs_calcsleeptime (drvr_globals, cmd_code));
    if ((readcmd = read->lancmd_reply_ptr) == NULL || rc == -EAGAIN) {
	if (retry_cnt++ < 3) {
	    lcs_debug_event (2, "retrying cmd_code=0x02x read_cmd=%p rc=%d\n",
			     (int) cmd_code, readcmd, rc);
	    goto Retry;
	} else {
	    lcs_debug_event (2, "lcs_lancmd retry count exceeded giving up\n");
	    goto Done;
	}
    }
    if (readcmd->cmd_code != cmd_code
	|| (writecmd->sequence_no - readcmd->sequence_no) > 3) {
	lcs_debug_event (1,
			 "readcmd=%p read_cmd_code=%02x write_cmd_code=%02x "
			 "read_seq=%04x write_seq=%04x\n",
			 readcmd, (int) readcmd->cmd_code, (int) cmd_code,
			 (int) readcmd->sequence_no,
			 (int) writecmd->sequence_no);
	goto Fail;
    } else {
	switch (cmd_code) {
	case lcs_lanstat:
	    if (readcmd->rel_adapter_no != rel_adapter_no) {
		lcs_debug_event (1, "rel adapters differ\n");
		goto Fail;
	    }
	    /* I got back a funny rel adapter no from stoplan so I'll assume 
	     * it's broke */
	case lcs_startlan:
	    if (readcmd->lan_type != writecmd->lan_type) {
		lcs_debug_event (1, "lan types differ\n");
		goto Fail;
	    }
	    if (readcmd->return_code != 0) {
		lcs_debug_event (2, "readcmd_return_code=%02x\n",
				 (int) readcmd->return_code);
		rc = -ENXIO;
		goto Done;
	    }
	case lcs_stoplan:
	    /* the stoplan primative should return the adapter type */
	    /* it dosen't appar to though */
	case lcs_startup:
	case lcs_shutdown:
#ifdef CONFIG_IP_MULTICAST
	case lcs_setipm:
	case lcs_delipm:
	case lcs_qipassist:
#endif
	    if (readcmd->return_code != 0)
		goto Fail;

	}
    }
Done:
    lcs_debug_event (2, "lcs_lancmd cmd_code=%02x rc=%d reply_ptr=%p\n",
		     (int) cmd_code, rc, read->lancmd_reply_ptr);
#if LCS_DEBUG
    if (rc && read->lancmd_reply_ptr)
	lcs_debug_display_read_buff ("reply", read->lancmd_reply_ptr);
#endif
    drvr_globals->state = lcs_idle;
    if (rc || (cmd_code != lcs_lanstat
#ifdef CONFIG_IP_MULTICAST
	       && cmd_code != lcs_qipassist
#endif
	))
	lcs_resume_read (read);
    return rc;
Fail:
    rc = -EIO;
    goto Done;
}

#ifdef CONFIG_IP_MULTICAST
int
lcs_print_ipassists (lcs_qipassist_ctlmsg * reply, char *name,
		     char *ipassist_str, lcs_assists mask)
{
    lcs_good_news ("%s: %s supported %s enabled %s\n",
		   name, ipassist_str,
		   reply->ip_assists_supported & mask ? "yes" : "no",
		   reply->ip_assists_enabled & mask ? "yes" : "no");
    return ((reply->ip_assists_supported & reply->
	     ip_assists_enabled & mask) ? TRUE : FALSE);
}
static int
lcs_check_multicast_supported (lcs_drvr_globals * drvr_globals)
{
    lcs_qipassist_ctlmsg *lcs_reply;
    char *name = drvr_globals->dev->name;
    int rc = FALSE;
    if (lcs_lancmd (lcs_qipassist, drvr_globals->rel_adapter_no,
		    drvr_globals LANCMD_DEFAULT_MCAST_PARMS) == 0) {
	lcs_reply = (lcs_qipassist_ctlmsg *)
	    drvr_globals->read->lancmd_reply_ptr;
	if (lcs_reply) {
	    lcs_print_ipassists (lcs_reply, name, "ip v6", lcs_ip_v6_support);
	    rc = lcs_print_ipassists (lcs_reply, name, "multicast",
				      lcs_multicast_support);
	}
    } else
	lcs_good_news
	    ("qipassist failed, ipassists assumed unsupported for %s\n", name);
    lcs_resume_read (drvr_globals->read);
    return (rc);
}
#endif

/* 
 * Update network statistics from hardware.
 */
int
lcs_gethwinfo (struct net_device *dev)
{
    lcs_drvr_globals *drvr_globals = ((lcs_drvr_globals *) dev->priv);
    lcs_lanstat_reply *lcs_reply;
    struct net_device_stats *stats;
    int rc;
    lcs_read_globals *read = drvr_globals->read;

    if ((rc = lcs_lancmd (lcs_lanstat,
			  drvr_globals->rel_adapter_no,
			  drvr_globals LANCMD_DEFAULT_MCAST_PARMS)) == 0) {

	lcs_reply = (lcs_lanstat_reply *)
	    read->lancmd_reply_ptr;
	if (lcs_reply) {
	    stats = &drvr_globals->stats;
	    stats->rx_packets = lcs_reply->num_packets_rx_from_lan;
	    stats->rx_errors = lcs_reply->num_rx_errors_detected;
	    stats->rx_length_errors = lcs_reply->num_rx_packets_too_large;
	    stats->rx_missed_errors = lcs_reply->num_rx_discarded_nobuffs_avail;
	    stats->tx_packets = lcs_reply->num_packets_tx_on_lan;
	    stats->tx_errors = lcs_reply->num_tx_errors_detected;
	    stats->tx_aborted_errors = lcs_reply->num_tx_packets_disgarded;
	}
    }
#if LCS_DEBUG
    else
	lcs_debug_event (2, "gethwinfo failed\n");
#endif
    lcs_resume_read (read);
    return rc;
}

/*
 * Displays the mac address etc. on startup.
 */
static void
lcs_displayinfo (struct net_device *dev)
{
    lcs_drvr_globals *drvr_globals = ((lcs_drvr_globals *) dev->priv);
    u8 *mac_address = dev->dev_addr;
    lcs_read_globals *read = drvr_globals->read;
    lcs_write_globals *write = drvr_globals->write;

    lcs_good_news
	("\nlcs: %s configured as follows read subchannel=%x "
	 "write subchannel=%x\n"
	 "read_devno=%04x write_devno=%04x\n"
	 "hw_address=%02X:%02X:%02X:%02X:%02X:%02X rel_adapter_no=%d\n",
	 dev->name, read->subchannel, write->subchannel, (int) read->devno,
	 (int) write->devno, (int) mac_address[0], (int) mac_address[1],
	 (int) mac_address[2], (int) mac_address[3], (int) mac_address[4],
	 (int) mac_address[5], drvr_globals->rel_adapter_no);
}

/* shutdown osa card */

int
lcs_halt_channels (lcs_drvr_globals * drvr_globals, int delay)
{
    int rc, rc2;
    if (delay & 1) {

	current->state = TASK_INTERRUPTIBLE;
	schedule_timeout (2);
    }
    rc = lcs_hsch (drvr_globals->read);
    if (delay & 2) {

	current->state = TASK_INTERRUPTIBLE;
	schedule_timeout (2);
    }
    drvr_globals->read->chan_busy_state = chan_dead;
    rc2 = lcs_hsch (drvr_globals->write);
    if (delay & 4) {

	current->state = TASK_INTERRUPTIBLE;
	schedule_timeout (2);
    }
    drvr_globals->write->chan_busy_state = chan_dead;
    return (rc ? rc : rc2);
}

static int
lcs_stopcard (lcs_drvr_globals * drvr_globals)
{
    int rc1, rc2, rc3;

    rc1 = rc2 = rc3 = 0;
    drvr_globals->up = FALSE;
    lcs_debug_event (2, "lcs_stopcard called\n");
    if (drvr_globals->state == lcs_interrupted)
	drvr_globals->state = lcs_idle;
    /* Make sure that we don't attempt to resume a channel */
    /* that was halted or dead to avoid the -ENOTCONNECTED */
    /* errors reported from Ingo on resume_IO */
    if (drvr_globals->read->chan_busy_state != chan_dead &&
	drvr_globals->write->chan_busy_state != chan_dead) {
	rc1 = lcs_lancmd (lcs_stoplan, drvr_globals->rel_adapter_no,
			  drvr_globals LANCMD_DEFAULT_MCAST_PARMS);
	/* We probably haven't got a good relative adapter no */
	if (drvr_globals->rel_adapter_no != lcs_invalid_adapter_no)
	    rc2 = lcs_lancmd (lcs_shutdown, 0, drvr_globals
			      LANCMD_DEFAULT_MCAST_PARMS);
    }
    lcs_resetstate (drvr_globals);
    rc3 = lcs_halt_channels (drvr_globals, 7);
    lcs_resetstate (drvr_globals);
    return (rc1 ? rc1 : (rc2 ? rc2 : rc3));
}

static void
lcs_initreadccws (lcs_read_globals * read)
{

    int cnt;
    ccw1_t *origccws, *ccws;
#if LCS_ZERO_COPY_READ
    struct sk_buff *curr_skb;
#else
    u8 *curr_iobuff;
#endif

    origccws = ccws = read->ccw;
    memset (ccws, 0, sizeof (ccw1_t) * LCS_NUM_RX_BUFFS + 1);
    for (cnt = 0; cnt < LCS_NUM_RX_BUFFS; cnt++, ccws++) {
	ccws->cmd_code = ccw_read;
	ccws->count = LCS_IOBUFFSIZE;
	ccws->flags = CCW_FLAG_CC | CCW_FLAG_SLI | CCW_FLAG_PCI;
#if LCS_ZERO_COPY_READ
	curr_skb = read->skb_buff[cnt];
	set_normalized_cda (ccws, virt_to_phys ((void *) curr_skb->data));
	((lcs_header_type *) curr_skb->data)->offset = LCS_ILLEGAL_OFFSET;
#else
	curr_iobuff = read->iobuff[cnt];
	set_normalized_cda (ccws, virt_to_phys ((void *) curr_iobuff));
	((lcs_header_type *) curr_iobuff)->offset = LCS_ILLEGAL_OFFSET;
#endif
    }
    ccws->cmd_code = ccw_transfer_in_channel1;
    (u32) ccws->cda = (u32) virt_to_phys (origccws);
    ccws = &read->ccw[LCS_NUM_RX_BUFFS - 1];
    ccws->flags = CCW_FLAG_CC | CCW_FLAG_SLI | CCW_FLAG_SUSPEND
#if LCS_USE_IDALS
	| (ccws->flags & CCW_FLAG_IDA)
#endif
	;
    read->lancmd_reply_ptr = NULL;
    read->rx_idx = 0;
    read->chan_busy_state = chan_starting_up;
}

static void
lcs_initwriteccws (lcs_write_globals * write)
{
    initccws (write->ccw, write->iobuff, LCS_NUM_TX_BUFFS, ccw_write, 0,
	      CCW_FLAG_CC | CCW_FLAG_SLI | CCW_FLAG_SUSPEND);
    write->prepare_idx = 0;
    write->tx_idx = LCS_NUM_TX_BUFFS - 1;
    write->resume_queued = FALSE;
    write->bytes_still_being_txed = write->pkts_still_being_txed = 0;
    write->adjusted_last_bytes_still_being_txed = 0;
    write->last_resume_time = lcs_get_tod ();
    write->chan_busy_state = chan_starting_up;
}

static void
lcs_startreadio (lcs_read_globals * read)
{
    lcs_debug_event (0, "lcs_startreadio read_globals=%p\n", read);
    lcs_initreadccws (read);
    lcs_doio (read, DOIO_DENY_PREFETCH | DOIO_ALLOW_SUSPEND
	      /*|DOIO_REPORT_ALL */ );

}

static void
lcs_startwriteio (lcs_write_globals * write)
{
    lcs_debug_event (0, "lcs_startwriteio write_globals=%p\n", write);
    lcs_initwriteccws (write);
    lcs_doio (write,
	      DOIO_DENY_PREFETCH | DOIO_ALLOW_SUSPEND /*|DOIO_REPORT_ALL */ );
}

static void
lcs_restartreadio (lcs_drvr_globals * drvr_globals)
{
#if LCS_USE_IDALS
    int cnt;
    ccw1_t *ccws;
#endif
#if LCS_USE_IDALS
    ccws = drvr_globals->read->ccw;
    for (cnt = 0; cnt < LCS_NUM_RX_BUFFS; cnt++, ccws++)
	clear_normalized_cda (ccws);
#endif
    lcs_wakeup (drvr_globals->read, -EAGAIN);
    lcs_startreadio (drvr_globals->read);
}

static void
lcs_queued_restartreadio (lcs_drvr_globals * drvr_globals)
{

    if (!lcs_drvr_globals_valid (drvr_globals))
	goto Done2;
    if (drvr_globals->state == lcs_idle
	|| drvr_globals->state == lcs_halting_subchannels)
	goto Done1;
    lcs_restartreadio (drvr_globals);
Done1:
    lcs_usage_free_drvr_globals (drvr_globals);
Done2:
    MOD_DEC_USE_COUNT;
}

static void
lcs_restartwriteio (lcs_drvr_globals * drvr_globals)
{
    lcs_write_globals *write;

    write = drvr_globals->write;
    write->bytes_still_being_txed = 0;
    if (!use_hw_stats) {
	drvr_globals->stats.tx_dropped += write->pkts_still_being_txed;
	drvr_globals->stats.tx_errors += write->pkts_still_being_txed;
	write->pkts_still_being_txed = 0;
    }
    switch (drvr_globals->state) {
    case lcs_doing_io:
	netif_start_queue (drvr_globals->dev);
	break;
    case lcs_idle_requesting_channels_for_lancmds:
    case lcs_requesting_channels_for_lancmds:
    case lcs_got_write_channel_for_lancmds:
	lcs_wakeup (write, 0);
	break;
    case lcs_doing_lancmds:
	lcs_wakeup (write, -EAGAIN);
	break;
    default:
    }
    lcs_startwriteio (write);
}

static void
lcs_queued_restartwriteio (lcs_drvr_globals * drvr_globals)
{
    if (!lcs_drvr_globals_valid (drvr_globals))
	goto Done2;
    if (drvr_globals->state == lcs_idle ||
	drvr_globals->state == lcs_halting_subchannels)
	goto Done1;
    lcs_restartwriteio (drvr_globals);
Done1:
    lcs_usage_free_drvr_globals (drvr_globals);
Done2:
    MOD_DEC_USE_COUNT;
}

void
lcs_startallio (lcs_drvr_globals * drvr_globals)
{
    if (drvr_globals->dev)
	netif_stop_queue (drvr_globals->dev);
    drvr_globals->state = lcs_idle_requesting_channels_for_lancmds;
    lcs_startreadio (drvr_globals->read);
    lcs_initqueue (drvr_globals->write);
    lcs_startwriteio (drvr_globals->write);
    lcs_sleepon (drvr_globals->write, 0);
    drvr_globals->state = lcs_doing_lancmds;
}

/*
 * The osa detection/startup routine called from lcs_probe on bootup & 
 * lcs_open this is called after the irq's are allocated & successful
 * sensing of the read & write subchannels.
 */

static int
lcs_detect (lcs_drvr_globals * drvr_globals, s16 forced_port_no,
	    u8 hint_port_no, u8 max_port_no, lcs_frame_type frame_type,
	    u8 * mac_address, int retrycnt)
{
    int rc, successful_startup = FALSE;
    int rel_adapter_no, rel_adapter_idx;
    lcs_read_globals *read;
    lcs_write_globals *write;
    lcs_lanstat_reply *lcs_reply;

    read = drvr_globals->read;
    write = drvr_globals->write;
#ifdef CONFIG_IP_MULTICAST
    spin_lock_init (&drvr_globals->ipm_lock);
#endif
    lcs_debug_event (1, "Entering detect read subchannel=%X\n",
		     (unsigned) drvr_globals->read->subchannel);
    if (drvr_globals->dev)
	netif_stop_queue (drvr_globals->dev);
    do {
	lcs_debug_event (1, "lcs_detect retrycnt=%d\n", retrycnt);
	if (retrycnt >= 0) {
	    if (retrycnt > 0)
		lcs_resetstate (drvr_globals);
	    if (retrycnt > 1) {
		/* Time to change tactics */
		rc = lcs_stopcard (drvr_globals);
	    } else
		rc = lcs_halt_channels (drvr_globals, retrycnt == 0 ? 0 : 7);
	    if (rc)
		continue;
	    lcs_startallio (drvr_globals);
	    if ((rc =
		 lcs_lancmd (lcs_startup, 0,
			     drvr_globals LANCMD_DEFAULT_MCAST_PARMS)))
		continue;
	    else
		successful_startup = TRUE;
	} else if ((rc = lcs_lgw_common (drvr_globals, lcs_startup)))
	    continue;
	else
	    successful_startup = TRUE;
	for (rel_adapter_idx = 0; rel_adapter_idx <= max_port_no &&
	     (rc == -ENXIO || rel_adapter_idx == 0); rel_adapter_idx++) {
	    if (forced_port_no != -1) {
		if (rel_adapter_idx > 0)
		    break;
		rel_adapter_no = forced_port_no;
	    } else {
		rel_adapter_no = (rel_adapter_idx == 0 ? hint_port_no :
				  (rel_adapter_idx ==
				   hint_port_no ? 0 : rel_adapter_idx));
	    }
	    lcs_debug_event (2, "Trying startlan rel adapter no %u\n",
			     (unsigned) rel_adapter_no);
	    rc = -1;
	    if (frame_type == lcs_autodetect_type) {
#ifdef CONFIG_NET_ETHERNET
		drvr_globals->lan_type = lcs_enet_type;
		rc = lcs_lancmd (lcs_startlan, rel_adapter_no,
				 drvr_globals LANCMD_DEFAULT_MCAST_PARMS);
		if (rc == 0)
		    goto lcs_good_detect;
#endif
#ifdef CONFIG_TR
		drvr_globals->lan_type = lcs_token_ring_type;
		rc = lcs_lancmd (lcs_startlan, rel_adapter_no,
				 drvr_globals LANCMD_DEFAULT_MCAST_PARMS);
		if (rc == 0)
		    goto lcs_good_detect;
#endif
#ifdef CONFIG_FDDI
		drvr_globals->lan_type = lcs_fddi_type;
		rc = lcs_lancmd (lcs_startlan, rel_adapter_no,
				 drvr_globals LANCMD_DEFAULT_MCAST_PARMS);
#endif
	    } else {
		drvr_globals->lan_type = frame_type;
		rc = lcs_lancmd (lcs_startlan, rel_adapter_no,
				 drvr_globals LANCMD_DEFAULT_MCAST_PARMS);
	    }
  lcs_good_detect:
	    if (rc == 0) {
		lcs_debug_event (2, "Found relative adapter number %d\n",
				 rel_adapter_no);
		drvr_globals->rel_adapter_no = rel_adapter_no;
      lcs_do_lanstat:
		rc = lcs_lancmd (lcs_lanstat,
				 drvr_globals->rel_adapter_no,
				 drvr_globals LANCMD_DEFAULT_MCAST_PARMS);
		if (!rc) {
		    lcs_reply = (lcs_lanstat_reply *)
			drvr_globals->read->lancmd_reply_ptr;
		    if (lcs_reply == NULL)
			goto lcs_do_lanstat;
		    memcpy (mac_address, lcs_reply->mac_address, LCS_ADDR_LEN);
		    lcs_resume_read (drvr_globals->read);
		    drvr_globals->up = TRUE;
		    return (0);
		}
	    }
	}

    } while ((rc == -EAGAIN || rc == -ETIMEDOUT) && retrycnt++ < 5);
    if (successful_startup && rc)
	lcs_bad_news
	    ("A partially successful startup read_devno=%04x write_devno=%4x "
	     "was detected\n"
	     "please check your configuration parameters,cables & connection "
	     "to the network.\n",
	     drvr_globals->read->devno, drvr_globals->write->devno);
    return (rc == EINTR ? -EINTR : -ENODEV);
}

/* Called by user doing ifconfig <network_device e.g. tr0> up */
int
lcs_open (struct net_device *dev)
{
    int rc = 0;
    lcs_drvr_globals *drvr_globals;
    lcs_read_globals *read;
    lcs_write_globals *write;

#if LCS_DEBUG_RESUME
    lcs_read_resume_cnt = lcs_write_resume_cnt = 0;
#endif
    drvr_globals = ((lcs_drvr_globals *) dev->priv);
    read = drvr_globals->read;
    write = drvr_globals->write;
    drvr_globals->state = lcs_idle;
    if (use_hw_stats == FALSE)
	memset (&drvr_globals->stats, 0, sizeof (struct net_device_stats));
    if ((rc = lcs_detect (drvr_globals, drvr_globals->rel_adapter_no, 0, 0,
			  drvr_globals->lan_type, dev->dev_addr, 0))) {
	lcs_debug_event (2, "Opening %s failed errcode=%d.\n", dev->name, rc);
	lcs_resetstate (drvr_globals);
    } else {
	MOD_INC_USE_COUNT;
	lcs_start_doing_io (drvr_globals);
	lcs_debug_event (2, "Successfully opened %s\n", dev->name);
#if LCS_DEBUG && !LCS_PRINTK_DEBUG
	/* When debugging we are usually only intrested till the
	 * device is open. */
	if (lcs_id && lcs_id->level != DEBUG_MAX_LEVEL)
	    debug_set_level (lcs_id, 0);
#endif
    }
    return rc;
}

/* Called by user doing ifconfig <network_device e.g. tr0> down */
int
lcs_close (struct net_device *dev)
{
    lcs_debug_event (2, "lcs_close called\n");
    netif_stop_queue (dev);
    lcs_stopcard ((lcs_drvr_globals *) dev->priv);
    MOD_DEC_USE_COUNT;
    return (0);
}

static int
lcs_txerror (char *message, struct net_device *dev)
{
    lcs_drvr_globals *drvr_globals = ((lcs_drvr_globals *) dev->priv);
    struct net_device_stats *stats = &drvr_globals->stats;
#if LCS_DEBUG_DISPLAY_MESSAGES
    lcs_write_globals *write = drvr_globals->write;
    lcs_debugchannel (message, write);
#endif
    if (!use_hw_stats) {
	stats->tx_dropped++;
	stats->tx_errors++;
    }
    return -EIO;
}

int
lcs_txpacket (struct sk_buff *skb, struct net_device *dev)
{
    lcs_drvr_globals *drvr_globals;
    lcs_write_globals *write;
    u16 pkt_len;
    lcs_header_type *tx_lcs_header_ptr;
    uint64_t tod, delta_tx;

    lcs_debug_event (2, "lcs_txpacket\n");
    if (dev == NULL || dev->priv == NULL) {
	lcs_debug_event (1, "lcs txpacket detected sick code\n");
	return -EINVAL;
    }
    drvr_globals = ((lcs_drvr_globals *) dev->priv);
    write = drvr_globals->write;
    tod = lcs_get_tod ();
    delta_tx = tod - write->last_lcs_txpacket_time;
    write->last_lcs_txpacket_time = tod;
    if (write->chan_busy_state == chan_dead ||
	write->chan_busy_state == chan_starting_up) {
	dst_link_failure (skb);
	dev_kfree_skb (skb);
	if (!use_hw_stats) {
	    struct net_device_stats *stats = &drvr_globals->stats;
	    stats->tx_dropped++;
	    stats->tx_errors++;
	    stats->tx_carrier_errors++;
	}
	return (0);
    }
    if (netif_is_busy (dev))
	return -EBUSY;
    if (!skb)
	return (lcs_txerror ("skb==NULL in lcs_txpacket\n", dev));
    lcs_debug_event (2, "skb_len %d\n", skb->len);
    pkt_len = skb->len;
#if 0
    /* This apparently can be omitted & saves us a lot of hassle */
    pkt_len = (ETH_ZLEN < skb->len ? skb->len : ETH_ZLEN);
#endif
    dev->trans_start = jiffies;
    lcs_chan_lock (write);
    {

	if ((tx_lcs_header_ptr = lcs_getfreetxbuff (write,
						    drvr_globals->lan_type,
						    drvr_globals->
						    rel_adapter_no,
						    pkt_len)) == NULL) {
	    netif_stop_queue (dev);
	    lcs_debug_event (1, "lcs_txpacket no tx buffers available\n");
	    lcs_resume_write (write);
	    lcs_chan_unlock (write);
	    return -EBUSY;
	}
	write->bytes_still_being_txed += pkt_len;
	if (!use_hw_stats)
	    write->pkts_still_being_txed++;
	memcpy (++tx_lcs_header_ptr, skb->data, pkt_len);
	dev_kfree_skb (skb);

	if (lcs_should_queue_write (write, tod)) {
	    lcs_queue_write_if_necessary (write);
	} else {
	    lcs_debug_event (1, "normal resume called\n");
	    lcs_resume_write (write);
	}
    }
    lcs_chan_unlock (write);
    return (0);
}

/*
 * Used by ifconfig & the proc filesystem to receive network statistics
 */
struct net_device_stats *
lcs_getstats (struct net_device *dev)
{
    lcs_drvr_globals *drvr_globals = (lcs_drvr_globals *) dev->priv;
    lcs_debug_event (2, "lcs_enet_stats\n");

    if (dev == NULL || dev->priv == NULL)
	return NULL;		/* shouldn't happen */
    if (use_hw_stats) {
	if (drvr_globals->doing_lanstat)
	    interruptible_sleep_on (&drvr_globals->lanstat_wait);
	else {
	    if (drvr_globals->state == lcs_doing_io) {
		drvr_globals->doing_lanstat = TRUE;
		lcs_gethwinfo (dev);
		drvr_globals->doing_lanstat = FALSE;
		netif_start_queue (dev);
		/* Gracefully start the lan again */
		drvr_globals->state = lcs_doing_io;
		/* Wake up processes awaiting lanstat results */
		wake_up_interruptible (&drvr_globals->lanstat_wait);
	    }
	}
    }
    return &((lcs_drvr_globals *) dev->priv)->stats;

}

#if !LCS_CHANDEV
/*
 * Used to setup kernel parameters for the osa card
 * read the printk's in the function for a description of how it
 * should be called.
 */
static __inline__ lcs_model_info *
lcs_get_model_info_by_idx (int idx)
{
    return (idx < LCS_CURR_NUM_MODELS ? &lcs_models[idx] :
	    (lcs_model_info *) & additional_model_info
	    [(idx - LCS_CURR_NUM_MODELS) << 1]);
}
static void
lcs_display_conf (void)
{
    int cu_idx;
    lcs_model_info *model_info;

    lcs_good_news ("\nlcs configured to use %s statistics,\n"
		   "ip checksumming of received packets is %s.\n"
		   "autodetection is %s ignore sense is %s.\n",
		   (use_hw_stats ? "hw" : "sw"),
		   (checksum_received_ip_pkts ? "on" : "off"),
		   (noauto ? "off" : "on"), (ignore_sense ? "on" : "off"));
    lcs_good_news ("configured to detect\n");
    for (cu_idx = 0; cu_idx < LCS_MAX_NUM_MODELS; cu_idx++) {
	model_info = lcs_get_model_info_by_idx (cu_idx);
	if ((model_info->max_rel_adapter_no == 0))
	    break;
	lcs_good_news ("cu_model 0x%02X,%d rel_adapter(s)\n",
		       model_info->cu_model, model_info->max_rel_adapter_no);
    }
}

#ifdef MODULE
MODULE_AUTHOR
("(C) 1999 IBM Corp. by DJ Barrow <djbarrow@de.ibm.com,barrow_dj@yahoo.com>");
MODULE_DESCRIPTION ("Linux for S/390 Lan Channel Station Network Driver");
MODULE_PARM (use_hw_stats, "i");
MODULE_PARM_DESC (use_hw_stats,
		  "Get network stats from LANSTAT lcs primitive as opposed to "
		  "doing it in software\n"
		  "this isn't used by MVS not recommended & dosen't appear to "
		  "work usually\n");
MODULE_PARM (checksum_received_ip_pkts, "i");
MODULE_PARM_DESC (checksum_received_ip_pkts,
		  "Do ip checksumming on inbound packets");

MODULE_PARM (additional_model_info, "0-" LCS_ADDITIONAL_MODELS_X2_STR "i");
MODULE_PARM_DESC (additional_model_info,
		  "This is made up of sets of model/max rel adapter no pairs\n"
		  "e.g. insmod additional_model_info=0x70,3 will look for "
		  "2 ports on a model 0x70.\n");
MODULE_PARM (devno_portno_pairs, "0-" LCS_MAX_FORCED_DEVNOS_X2_STR "i");
MODULE_PARM_DESC (devno_portno_pairs, "devno,rel_adapter_no pairs\n"
		  "rel adapter no of -1 indicates don't use this adapter.");
MODULE_PARM (noauto, "i");
MODULE_PARM_DESC (noauto,
		  "set this equal to 1 if you want autodetection off, "
		  "this means you have\n"
		  "explicitly configure LCS devices with the "
		  "devno_portno_pairs module parameter\n.");
MODULE_PARM (ignore_sense, "i");
MODULE_PARM_DESC (ignore_sense,
		  "set this to 1 if you wish to ignore sense information "
		  "from the device layer for\n"
		  "non auto detected devices.\n");
#else

#if LINUX_VERSION_CODE>=KERNEL_VERSION(2,3,0)
static int __init
lcs_setup_models (char *str)
#else
void __init
lcs_setup_models (char *str, int *ints)
#endif
{
    int max_parms = LCS_MAX_NUM_PARMS - (LCS_CURR_NUM_MODELS << 1);
    int num_new_models;
#if  LINUX_VERSION_CODE>=KERNEL_VERSION(2,3,0)
    int ints[(LCS_ADDITIONAL_MODELS << 1) + 1];
    get_options (str, (LCS_ADDITIONAL_MODELS << 1), ints);
#endif
    num_new_models = (ints[0] - 2);
    if (num_new_models < 0)
	num_new_models = 0;
    else if (num_new_models & 1)
	num_new_models = -1;
    else
	num_new_models >>= 1;
    if ((ints[0] >= 1 && ints[0] <= max_parms) && num_new_models >= 0) {
	use_hw_stats = ints[1];
	if (ints[0] >= 2)
	    checksum_received_ip_pkts = ints[2];
	if (num_new_models) {
	    memcpy (&additional_model_info[0], &ints[3],
		    num_new_models * sizeof (lcs_model_info));
	}
	lcs_display_conf ();
    } else {
	lcs_bad_news ("lcs_setup: incorrect number of arguments\n");
	printk
	    ("Usage: lcs=hw-stats,ip-checksumming,additional cu model "
	     "/max rel adapter no. pairs. \n");
	printk
	    ("    The driver now auto detects how the card is configured.\n");
	printk ("    e.g lcs=1,1,0x60,2\n");
	printk
	    ("    Will collect network stats from LANSTAT lcs primitive "
	     "as opposed\n");
	printk
	    ("    to doing it in software for ifconfig (this isn't used by "
	     "MVS & doesn't\n");
	printk ("    to be widely supported, ip checksum received packets\n");
	printk ("    and detect 0x3088 model 0x60 devices\n");
	printk
	    ("    will look for 2 ports per card associated with this model.\n");
	printk
	    ("    The default is to use sw stats, with ip checksumming off,\n");
	printk ("    this improves performance, network hw uses a crc32\n");
	printk
	    ("    ( crc64 for fddi ) which should be adequete to gaurantee\n");
	printk ("    integrity for normal use\n");
	printk
	    ("    however, financial institutions etc. may like the "
	     "additional\n");
	printk ("    security of ip checksumming.\n");
    }
#if LINUX_VERSION_CODE>=KERNEL_VERSION(2,3,0)
    return (1);
#endif
}

#if LINUX_VERSION_CODE>=KERNEL_VERSION(2,3,0)
static int __init
lcs_setup_devnos (char *str)
#else
void __init
lcs_setup_devnos (char *str, int *ints)
#endif
{
    int fixed_devnos;
#if  LINUX_VERSION_CODE>=KERNEL_VERSION(2,3,0)
    int ints[(LCS_MAX_FORCED_DEVNOS << 1) + 1];
    get_options (str, (LCS_MAX_FORCED_DEVNOS << 1), ints);
#endif
    fixed_devnos = ints[0];
    if ((fixed_devnos & 1) == 0 && fixed_devnos > 0) {
	memcpy (devno_portno_pairs, &ints[1], sizeof (int) * fixed_devnos);
    } else {
	lcs_bad_news ("lcs_setup: incorrect number of arguments\n");
	printk ("usage lcs_devno=devno,rel_adapter_no pairs\n");
	printk ("rel adapter no of -1 indicates don't use this adapter.\n");
    }
#if LINUX_VERSION_CODE>=KERNEL_VERSION(2,3,0)
    return (1);
#endif
}

#if LINUX_VERSION_CODE>=KERNEL_VERSION(2,3,0)
static int __init
lcs_noauto (char *str)
#else
void __init
lcs_noauto (char *str, int *ints)
#endif
{
    noauto = 1;
#if LINUX_VERSION_CODE>=KERNEL_VERSION(2,3,0)
    return (1);
#endif
}

#if LINUX_VERSION_CODE>=KERNEL_VERSION(2,3,0)
static int __init
lcs_ignore_sense (char *str)
#else
void __init
lcs_ignore_sense (char *str, int *ints)
#endif
{
    ignore_sense = 1;
#if LINUX_VERSION_CODE>=KERNEL_VERSION(2,3,0)
    return (1);
#endif
}

#if LINUX_VERSION_CODE>=KERNEL_VERSION(2,3,0)
__setup ("lcs_devno=", lcs_setup_devnos);
__setup ("lcs=", lcs_setup_models);
__setup ("lcs_noauto", lcs_noauto);
__setup ("lcs_ignore_sense", lcs_ignore_sense);
#endif
#endif

static lcs_inline int
lcs_model_supported (s390_dev_info_t * dev_info_ptr, int *forced_rel_adapter_no)
{
    int cu_idx, dev_port_idx;
    lcs_model_info *model_info;
    lcs_dev_portno_pair *dev_portno;

    *forced_rel_adapter_no = -1;
    for (dev_port_idx=0; dev_port_idx<LCS_MAX_FORCED_DEVNOS; dev_port_idx++) {
	dev_portno = &lcs_dev_portno[dev_port_idx];
	if (dev_portno->portno == -2)
	    break;
	/*
	   Check are we dealing with the device passed in as a parameter.
	 */
	if ((dev_portno->devno & 0xfffe) == (dev_info_ptr->devno & 0xfffe)) {
	    /* portno=-1 we want to ignore this device */
	    if (dev_portno->portno == -1)
		return (-1);
	    else {
		*forced_rel_adapter_no = dev_portno->portno;
		break;
	    }
	}
    }
    if (noauto && *forced_rel_adapter_no == -1)
	return (-1);
    if (ignore_sense)
	return (*forced_rel_adapter_no);
    if (dev_info_ptr->sid_data.cu_type != LCS_CUTYPE)
	return (-1);
    for (cu_idx = 0; cu_idx < LCS_MAX_NUM_MODELS; cu_idx++) {
	model_info = lcs_get_model_info_by_idx (cu_idx);
	if (model_info->max_rel_adapter_no == 0)
	    break;
	if (model_info->cu_model == dev_info_ptr->sid_data.cu_model) {
	    if (*forced_rel_adapter_no == -1)
		return (model_info->max_rel_adapter_no);
	    else
		return (*forced_rel_adapter_no);
	}
    }
    return (-1);
}

#endif				/* LCS_CHANDEV */

#if !LCS_CHANDEV
void
lcs_unregister (struct net_device *dev, u8 lan_type)
{
    if (dev) {
	switch (lan_type) {
#ifdef CONFIG_FDDI
	case lcs_fddi_type:
	    unregister_netdev (dev);
	    break;
#endif
#ifdef CONFIG_NET_ETHERNET
	case lcs_enet_type:
	    unregister_netdev (dev);
	    break;
#endif
#ifdef CONFIG_TR
	case lcs_token_ring_type:
	    unregister_trdev (dev);
	    break;
#endif
	default:
	    lcs_bad_news("lcs_unregister attempt to unregister %p "
			 "unknown device type\n",dev);
	    return;
	}

    }
}
#endif
#ifdef CONFIG_IP_MULTICAST
static int
lcs_fix_multicast_list (lcs_drvr_globals * drvr_globals)
{
    lcs_ipm_list *curr_lmem;
    lcs_state oldstate;

    lcs_daemonize ("lcs_fix_multi", 0, TRUE);
    if (!lcs_drvr_globals_valid (drvr_globals))
	goto Done2;
    if (drvr_globals->up == FALSE)
	goto Done1;
    oldstate = drvr_globals->state;
    spin_lock (&drvr_globals->ipm_lock);
    for (curr_lmem = drvr_globals->ipm_list; curr_lmem;
	 curr_lmem = curr_lmem->next) {
	switch (curr_lmem->ipm_state) {
	case ipm_set_required:
	    spin_unlock (&drvr_globals->ipm_lock);
	    if (lcs_lancmd
		(lcs_setipm, drvr_globals->rel_adapter_no, drvr_globals, 1,
		 &curr_lmem->ipm)) {
		lcs_good_news("lcs_fix_multicast_list Failed to add "
			      "multicast entry %x\n",
			      (u32) curr_lmem->ipm.ip_addr);
	    } else
		curr_lmem->ipm_state = ipm_on_card;
	    spin_lock (&drvr_globals->ipm_lock);
	    break;
	case ipm_delete_required:
	    spin_unlock (&drvr_globals->ipm_lock);
	    lcs_lancmd (lcs_delipm, drvr_globals->rel_adapter_no, drvr_globals,
			1, &curr_lmem->ipm);
	    spin_lock (&drvr_globals->ipm_lock);
	    if (remove_from_list ((list **) & drvr_globals->ipm_list,
				  (list *) curr_lmem))
		kfree (curr_lmem);
	    else
		lcs_bad_news
		    ("lcs_fix_multicast_list detected nonexistant entry"
		     " ipm_list=%p member %p\n", drvr_globals->ipm_list,
		     curr_lmem);
	    break;
	case ipm_on_card:
	    break;
	}
    }
    spin_unlock (&drvr_globals->ipm_lock);
    drvr_globals->state = oldstate;
    if (oldstate == lcs_doing_io && drvr_globals->dev)
	netif_start_queue (drvr_globals->dev);
Done1:
    atomic_set (&drvr_globals->kernel_thread_lock, 1);
    lcs_usage_free_drvr_globals (drvr_globals);
Done2:
    MOD_DEC_USE_COUNT;
    return (0);
}

/*
  Owing to a quirk in the osa microcode it is incompatible with its API's 
  & we can only add or delete a single ipm entry at a time.
  We are also called with the spin_lock_bh held meaning we in_interrupt is true
  to add & delete multicast entries to get around this we indirectly start
  a kernel thread which executes the io commands necessary at a later
  time.
  We also need ip addresses for the setipm & delipm primatives
  to do this we use arp_mc_map.
  N.B. We even need to kmalloc GFP_ATOMIC here

 */
static void
lcs_set_multicast_list (struct net_device *dev)
{
    lcs_drvr_globals *drvr_globals;
    struct ip_mc_list *im4;
    struct in_device *in4_dev;
    int addr_in_list;

#if 0				/* LCS doesn't do IPV6 yet. */
    struct ifmcaddr6 *im6;
    struct inet6_dev *in6_dev;
#endif
    char buf[MAX_ADDR_LEN];
    lcs_ipm_list *curr_lmem, *new_lmem;
    lcs_state oldstate;

    if ((dev == NULL) || (dev->priv == NULL)) {
	lcs_bad_news ("invalid dev=%p in lcs_set_multicast_list\n", dev);
	return;
    }
    drvr_globals = (lcs_drvr_globals *) dev->priv;
    oldstate = drvr_globals->state;
#if LINUX_VERSION_CODE >=KERNEL_VERSION(2,3,0)
    if ((in4_dev = in_dev_get (dev)) == NULL)
	return;
    read_lock (&in4_dev->lock);
#else
    if ((in4_dev = dev->ip_ptr) == NULL)
	return;
#endif
    /* Pass 1 find the entries to be deleted */
    /* This is done first to avoid overflowing the cards */
    /* onboard multicast list. */
    spin_lock (&drvr_globals->ipm_lock);
    for (curr_lmem = drvr_globals->ipm_list; curr_lmem;
	 curr_lmem = curr_lmem->next) {
	addr_in_list = FALSE;
	for (im4 = in4_dev->mc_list; im4; im4 = im4->next) {
	    arp_mc_map (im4->multiaddr, buf, dev, 0);
	    if ((memcmp (buf, &curr_lmem->ipm.mac_address,
			 LCS_ADDR_LEN) == 0) &&
		(curr_lmem->ipm.ip_addr == im4->multiaddr)) {
		addr_in_list = TRUE;
		break;
	    }

	}
	if (!addr_in_list)
	    curr_lmem->ipm_state = ipm_delete_required;
    }
    /* Pass 2 find the new entries to be added */
    for (im4 = in4_dev->mc_list; im4; im4 = im4->next) {
	arp_mc_map (im4->multiaddr, buf, dev, 0);
	addr_in_list = FALSE;
	for (curr_lmem = drvr_globals->ipm_list; curr_lmem;
	     curr_lmem = curr_lmem->next) {
	    if ((memcmp (buf, &curr_lmem->ipm.mac_address, LCS_ADDR_LEN) == 0)
		&& (curr_lmem->ipm.ip_addr == im4->multiaddr)) {
		addr_in_list = TRUE;
		break;
	    }
	}
	if (!addr_in_list) {
	    new_lmem =
		(lcs_ipm_list *) kmalloc (sizeof (lcs_ipm_list), GFP_ATOMIC);
	    if (!new_lmem) {
		lcs_good_news("lcs_set_multicast_list Failed to add "
			      "multicast entry %x\n",
			      (u32) im4->multiaddr);
		goto done;
	    }
	    memset (new_lmem, 0, sizeof (lcs_ipm_list));
	    memcpy (&new_lmem->ipm.mac_address, buf, LCS_ADDR_LEN);
	    new_lmem->ipm.ip_addr = im4->multiaddr;
	    new_lmem->ipm_state = ipm_set_required;
	    add_to_list ((list **) & drvr_globals->ipm_list, (list *) new_lmem);
	}
    }
#if LINUX_VERSION_CODE >=KERNEL_VERSION(2,3,0)
    read_unlock (&in4_dev->lock);
    in_dev_put (in4_dev);
#endif
#if 0				/* LCS doesn't do IPV6 yet */
    if ((in6_dev = in6_dev_get (dev)) == NULL)
	return;
    read_lock_bh (&in6_dev->lock);
    for (im6 = in6_dev->mc_list; im6; im6 = im6->next) {
    }
    read_unlock (&in4_dev->lock);
    in6_dev_put (idev);
#endif
done:
    spin_unlock (&drvr_globals->ipm_lock);
    lcs_queue_thread (lcs_fix_multicast_list, drvr_globals);
}
#endif

void
lcs_set_slow_hw (lcs_drvr_globals * drvr_globals, u16 cu_type, u8 cu_model)
{
    drvr_globals->slow_hw =
	((cu_type == 0x3088
	  && (cu_model == 0x8 || cu_model == 0x1f)) ? TRUE : FALSE);
}

#if LCS_DEBUG && !LCS_PRINTK_DEBUG

static void
lcs_debug_register (void)
{
    if (lcs_id == NULL) {
	lcs_id = debug_register ("lcs", 1, 16, 16 * sizeof (long));
	debug_register_view (lcs_id, &debug_sprintf_view);
	debug_set_level (lcs_id, 0);
    }
}
#endif

/*
 * The main device detection routine called  on bootup or module
 * installation to do. An unfortunately unwieldly function which dosen't
 * lend itself easily to being broken into smaller functional blocks &
 * my reason for hating tabs of 8. 
 */

#if LCS_CHANDEV
int
lcs_probe (chandev_probeinfo * probeinfo)
{
    lcs_drvr_globals *drvr_globals = NULL;
    int rc = -ENODEV;
    lcs_write_globals *write;
    lcs_read_globals *read;
    u8 mac_address[LCS_ADDR_LEN];
    struct net_device *dev = NULL;
    char *basename = NULL;
    struct net_device *(*init_netdevfunc) (struct net_device * dev,
					   int sizeof_priv) = NULL;
    void (*unreg_netdevfunc) (struct net_device * dev) = NULL;

    drvr_globals = lcs_alloc_drvr_globals ();
    if (!drvr_globals) {
	lcs_bad_news ("lcs_probe kmalloc failed\n");
	return -ENOMEM;
    }
    read = drvr_globals->read;
    write = drvr_globals->write;
    read->subchannel = probeinfo->read.irq;
    write->subchannel = probeinfo->write.irq;
    read->devno = probeinfo->read.devno;
    write->devno = probeinfo->write.devno;
    lcs_set_slow_hw (drvr_globals, probeinfo->read.cu_type,
		     probeinfo->read.cu_model);
    if (lcs_allocirq (read) || lcs_allocirq (write)) {
	rc = -ENODEV;
	goto Fail;
    }
    checksum_received_ip_pkts = probeinfo->checksum_received_ip_pkts;
    use_hw_stats = probeinfo->use_hw_stats;
    rc = lcs_detect (drvr_globals, probeinfo->port_protocol_no,
		     probeinfo->hint_port_no, probeinfo->max_port_no,
		     lcs_autodetect_type, mac_address, 0);

    if (rc) {
	lcs_stopcard (drvr_globals);
	goto Fail;
    } else {
	/*Fill in the fields of the device structure 
	 * with interface specific values.
	 */
	switch (drvr_globals->lan_type) {
#ifdef CONFIG_NET_ETHERNET
	case lcs_enet_type:
	    init_netdevfunc = init_etherdev;
	    unreg_netdevfunc = unregister_netdev;
	    basename = "eth";
	    drvr_globals->lan_type_trans = eth_type_trans;
	    break;
#endif
#ifdef CONFIG_TR
	case lcs_token_ring_type:
	    init_netdevfunc = init_trdev;
	    unreg_netdevfunc = unregister_netdev;
	    basename = "tr";
	    drvr_globals->lan_type_trans = tr_type_trans;
	    break;
#endif
#ifdef CONFIG_FDDI
	case lcs_fddi_type:
	    init_netdevfunc = init_fddidev;
	    unreg_netdevfunc = unregister_netdev;
	    basename = "fddi";
	    drvr_globals->lan_type_trans = fddi_type_trans;
	    break;
#endif
	}
	if ((dev =
	     chandev_initnetdevice (probeinfo, drvr_globals->rel_adapter_no,
				    NULL, 0, basename, init_netdevfunc,
				    unreg_netdevfunc)) == 0)
	    goto Fail;
	memcpy (dev->dev_addr, mac_address, LCS_ADDR_LEN);
	dev->open = lcs_open;
	dev->stop = lcs_close;
	dev->hard_start_xmit = lcs_txpacket;
	dev->get_stats = lcs_getstats;

	drvr_globals->dev = dev;

	dev->priv = drvr_globals;
#ifdef CONFIG_IP_MULTICAST
	dev->set_multicast_list = (lcs_check_multicast_supported (drvr_globals)
				   ? lcs_set_multicast_list : NULL);
#endif
	lcs_stopcard (drvr_globals);
	netif_stop_queue (dev);
	lcs_displayinfo (dev);
	lcs_debug_exception (0, "device structure=%p drvr globals=%p\n"
			     "read=%p write=%p\n"
			     "read_ccws=%p  write_cwws=%p\n", dev, drvr_globals,
			     drvr_globals->read, drvr_globals->write,
			     &drvr_globals->read->ccw[0],
			     &drvr_globals->write->ccw[0]);
	return 0;
    }
Fail:
    if (drvr_globals) {
	lcs_freeallirqs (drvr_globals);
	lcs_usage_free_drvr_globals (drvr_globals);
    }
    lcs_debug_event (2, "osa_probe error %d\n", rc);
    return rc;
}
#else
int __init
lcs_probe (void)
{
    int read_irq, write_irq;
    s390_dev_info_t read_devinfo, write_devinfo;
    lcs_drvr_globals *drvr_globals = NULL;
    int hint, detect_error;
    int rc = -ENODEV;
    int max_rel_adapter_no;
    int err;
    int forced_rel_adapter_no;
    int lcs_loopcnt = 0;
    u8 mac_address[LCS_ADDR_LEN];
    struct net_device *dev = NULL;
    lcs_frame_type lan_type = lcs_autodetect_type;
    lcs_write_globals *write;
    lcs_read_globals *read;

    lcs_debug_event (1, "Starting lcs_probe dev=%p\n", dev);

    for (read_irq = get_irq_first (); read_irq >= 0;
	 read_irq = get_irq_next (read_irq)) {
	/* check read channel
	 * we had to do the cu_model check also because ctc devices
	 * have the same cutype & after asking some people
	 * the model numbers are given out pseudo randomly so
	 * we can't just take a range of them also the dev_type & models are 0
	 */
	lcs_loopcnt++;
	if (lcs_loopcnt > 0x10000) {
	    lcs_bad_news
		("lcs probe detected infinite loop bug in get_irq_next\n");
	    goto Fail;
	}
	if ((err = get_dev_info_by_irq (read_irq, &read_devinfo))) {
	    lcs_bad_news
		("lcs_probe get_dev_info_by_irq reported err=%X on irq %d\n"
		 "should not happen\n", err, read_irq);
	    continue;
	}
	if (read_devinfo.status & DEVSTAT_DEVICE_OWNED)
	    continue;
	if (read_devinfo.devno & 1)
	    continue;
	if ((max_rel_adapter_no =
	     lcs_model_supported (&read_devinfo, &forced_rel_adapter_no)) < 0)
	    continue;
	if (get_dev_info_by_devno (read_devinfo.devno + 1, &write_devinfo))
	    continue;
	if (write_devinfo.status & DEVSTAT_DEVICE_OWNED)
	    continue;
	write_irq = write_devinfo.irq;
	if ((ignore_sense == FALSE && forced_rel_adapter_no != -1) &&
	    ((write_devinfo.sid_data.cu_type != read_devinfo.sid_data.cu_type)
	     || (write_devinfo.sid_data.cu_model !=
		 read_devinfo.sid_data.cu_model)))
	    continue;
	if (drvr_globals == NULL) {
	    drvr_globals = lcs_alloc_drvr_globals ();
	    if (!drvr_globals) {
		lcs_bad_news ("lcs_probe kmalloc failed\n");
		return -ENOMEM;
	    }
	}
	read = drvr_globals->read;
	write = drvr_globals->write;
	read->subchannel = read_irq;
	write->subchannel = write_irq;
	read->devno = read_devinfo.devno;
	write->devno = write_devinfo.devno;
	lcs_set_slow_hw (drvr_globals, read_devinfo.sid_data.cu_type,
			 read_devinfo.sid_data.cu_model);
	if (lcs_allocirq (read) || lcs_allocirq (write)) {
	    lcs_freeallirqs (drvr_globals);
	    /* We may find another card further up the chain */
	} else {
	    if (forced_rel_adapter_no == -1) {
		hint = (read_devinfo.devno & 0xFF) >> 1;
		if (hint > max_rel_adapter_no) {
		    /* The card is possibly emulated e.g P/390 */
		    /* or possibly configured to use a shared */
		    /* port configured by osa-sf. */
		    hint = 0;
		}
	    } else {
		hint = -1;
	    }
	    detect_error =
		lcs_detect (drvr_globals, forced_rel_adapter_no, hint,
			    max_rel_adapter_no, lan_type, mac_address, 0);
	    if (detect_error) {
		lcs_stopcard (drvr_globals);
		lcs_freeallirqs (drvr_globals);
	    } else {

		/*Fill in the fields of the device structure 
		 * with interface specific values.
		 */
		switch (drvr_globals->lan_type) {
#ifdef CONFIG_NET_ETHERNET
		case lcs_enet_type:
		    dev = init_etherdev (dev, 0);
		    drvr_globals->lan_type_trans = eth_type_trans;
		    break;
#endif
#ifdef CONFIG_TR
		case lcs_token_ring_type:
		    dev = init_trdev (dev, 0);
		    drvr_globals->lan_type_trans = tr_type_trans;
		    break;
#endif
#ifdef CONFIG_FDDI
		case lcs_fddi_type:
/* I'm not sure whether this function is in the generic kernel anymore */
		    dev = init_fddidev (dev, 0);
		    drvr_globals->lan_type_trans = fddi_type_trans;
		    break;
#endif
		}
		if (dev)
		    memcpy (dev->dev_addr, mac_address, LCS_ADDR_LEN);
		else {
		    rc = -ENOMEM;
		    goto Fail;
		}
		dev->open = lcs_open;
		dev->stop = lcs_close;
		dev->hard_start_xmit = lcs_txpacket;
		dev->get_stats = lcs_getstats;
		drvr_globals->dev = dev;
		dev->priv = drvr_globals;
#ifdef CONFIG_IP_MULTICAST
		dev->set_multicast_list =
		    (lcs_check_multicast_supported (drvr_globals)
		     ? lcs_set_multicast_list : NULL);
#endif
		lcs_stopcard (drvr_globals);
		netif_stop_queue (dev);
		lcs_displayinfo (dev);
		lcs_debug_event (0, "device structure=%p drvr globals=%p\n"
				 "read=%p write=%p\n"
				 "read_ccws=%p  write_cwws=%p\n", dev,
				 drvr_globals, drvr_globals->read,
				 drvr_globals->write,
				 &drvr_globals->read->ccw[0],
				 &drvr_globals->write->ccw[0]);

		return 0;
	    }
	}
    }
Fail:
    if (drvr_globals) {

	lcs_freeallirqs (drvr_globals);
	lcs_usage_free_drvr_globals (drvr_globals);

    }
    lcs_debug_event (2, "osa_probe error %d\n", rc);
    return rc;
}
#endif

#if LCS_CHANDEV || MODULE
static int
lcs_shutdown_card (struct net_device *dev)
{
    lcs_drvr_globals *drvr_globals = ((lcs_drvr_globals *) dev->priv);

#if !LCS_CHANDEV
    lcs_unregister (dev, drvr_globals->lan_type);	
    /* unregister_netdev calls close */
#endif
    lcs_freeallirqs (drvr_globals);
    lcs_usage_free_drvr_globals (drvr_globals);
    kfree (dev);
    return (0);
}
#endif

#if LCS_CHANDEV
static void
lcs_msck_notification_func (struct net_device *dev, int msck_irq,
			    chandev_msck_status prevstatus,
			    chandev_msck_status newstatus)
{
    lcs_drvr_globals *drvr_globals = (lcs_drvr_globals *) dev->priv;

    if (drvr_globals) {
	lcs_debug_event (0,"lcs_msck_notifcation_func drvr_globals=%p "
			 "msck_irq=%d prevstatus=%d newstatus=%d",
			 drvr_globals, msck_irq, prevstatus, newstatus);
	if (newstatus == chandev_status_good
	    || newstatus == chandev_status_all_chans_good) {
	    switch (prevstatus) {
	    case chandev_status_no_path:
	    case chandev_status_gone:
	    case chandev_status_not_oper:
		if (msck_irq == drvr_globals->read->subchannel)
		    lcs_restartreadio (drvr_globals);
		else
		    lcs_restartwriteio (drvr_globals);
		break;
	    default:
	    }
	} else if (msck_irq == drvr_globals->write->subchannel
		   && newstatus != chandev_status_revalidate)
	    drvr_globals->write->chan_busy_state = chan_dead;

    } else
	lcs_debug_exception (0, "lcs_msck_notification_func drvr_globals=NULL");
}
#endif

#if MODULE
static void
lcs_cleanup (void)
{
#if LCS_CHANDEV
    chandev_unregister (lcs_probe, TRUE);
#else
    while (lcs_card_list)
	lcs_shutdown_card (lcs_card_list->dev);
#endif
}
#endif

#if MODULE
int
init_module (void)
#else
#if LINUX_VERSION_CODE >=KERNEL_VERSION(2,3,0)
static
#endif
    int __init
lcs_init (void)
#endif
{
    int cardsfound = 0;

    lcs_good_news ("Starting lcs\n");
#if LCS_DEBUG && !LCS_PRINTK_DEBUG
    lcs_debug_register ();
#endif

#if LCS_CHANDEV
    cardsfound =
	chandev_register_and_probe (lcs_probe,
				    (chandev_shutdownfunc) lcs_shutdown_card,
				    (chandev_msck_notification_func)
				    lcs_msck_notification_func,
				    chandev_type_lcs);
#else
    while ((lcs_probe () == 0)) {
	cardsfound++;
    }
#endif
    if (!cardsfound) {
	lcs_bad_news ("No lcs capable cards found\n");
#if MODULE
	cleanup_module ();
#endif
    }
#if !LCS_CHANDEV
    else {
	lcs_display_conf ();
    }
#endif
    return (cardsfound ? 0 : -ENODEV);
}

/*
 * Code for module loading & unloading
 */

#ifdef MODULE
void
cleanup_module (void)
{
    lcs_good_news ("Terminating lcs module.\n");
    lcs_cleanup ();
#if LCS_DEBUG && !LCS_PRINTK_DEBUG
    if (lcs_id) {
	debug_unregister_view (lcs_id, &debug_sprintf_view);
	debug_unregister (lcs_id);
	lcs_id = NULL;
    }
#endif
}
#else
#if LINUX_VERSION_CODE>=KERNEL_VERSION(2,2,18)
__initcall (lcs_init);
#endif
#endif
