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