* [patch 0/4] s390: ctc patches for 2.6.25 (2nd try)
@ 2008-02-06 11:55 Ursula Braun
2008-02-06 11:55 ` [patch 1/4] ctc / netiucv: consolidate fsm_action_nop Ursula Braun
` (3 more replies)
0 siblings, 4 replies; 8+ messages in thread
From: Ursula Braun @ 2008-02-06 11:55 UTC (permalink / raw)
To: jgarzik, netdev, linux-s390
--
Jeff,
sorry for my violation of the git-bisect rule. This is now my 2nd try ...
The following patches are intended for 2.6.25. Besides clean-ups
they replace the old ctc driver by a reworked ctcm driver.
This ctcm driver supports the channel-to-channel connections of the
old ctc driver plus an additional MPC protocol to provide SNA
connectivity.
Patch 1/4: clean-ups in ctc and netiucv
Patch 2/4: clean-ups in Kconfig
Patch 3/4: reworked ctc driver
Patch 4/4: removal of old ctc driver
Regards, Ursula Braun
^ permalink raw reply [flat|nested] 8+ messages in thread* [patch 1/4] ctc / netiucv: consolidate fsm_action_nop 2008-02-06 11:55 [patch 0/4] s390: ctc patches for 2.6.25 (2nd try) Ursula Braun @ 2008-02-06 11:55 ` Ursula Braun 2008-02-06 11:55 ` [patch 2/4] drivers/s390/net: Kconfig brush up Ursula Braun ` (2 subsequent siblings) 3 siblings, 0 replies; 8+ messages in thread From: Ursula Braun @ 2008-02-06 11:55 UTC (permalink / raw) To: jgarzik, netdev, linux-s390; +Cc: Peter Tiedemann [-- Attachment #1: 700-fsm.diff --] [-- Type: text/plain, Size: 2232 bytes --] From: Peter Tiedemann <ptiedem@de.ibm.com> move fsm_action_nop to fsm.h to avoid duplicate definitions in both drivers ctc and netiucv. Signed-off-by: Peter Tiedemann <ptiedem@de.ibm.com> Signed-off-by: Ursula Braun <braunu@de.ibm.com> --- drivers/s390/net/ctcmain.c | 8 -------- drivers/s390/net/fsm.h | 8 ++++++++ drivers/s390/net/netiucv.c | 8 +------- 3 files changed, 9 insertions(+), 15 deletions(-) Index: linux-2.6-uschi/drivers/s390/net/fsm.h =================================================================== --- linux-2.6-uschi.orig/drivers/s390/net/fsm.h +++ linux-2.6-uschi/drivers/s390/net/fsm.h @@ -260,4 +260,12 @@ extern int fsm_addtimer(fsm_timer *timer */ extern void fsm_modtimer(fsm_timer *timer, int millisec, int event, void *arg); +/** + * NOP action for statemachines + */ +static inline void +fsm_action_nop(fsm_instance *fi, int event, void *arg) +{ +} + #endif /* _FSM_H_ */ Index: linux-2.6-uschi/drivers/s390/net/netiucv.c =================================================================== --- linux-2.6-uschi.orig/drivers/s390/net/netiucv.c +++ linux-2.6-uschi/drivers/s390/net/netiucv.c @@ -137,6 +137,7 @@ PRINT_##importance(header "%02x %02x %02 #define PRINTK_HEADER " iucv: " /* for debugging */ static struct device_driver netiucv_driver = { + .owner = THIS_MODULE, .name = "netiucv", .bus = &iucv_bus, }; @@ -571,13 +572,6 @@ static void netiucv_callback_connres(str fsm_event(conn->fsm, CONN_EVENT_CONN_RES, conn); } -/** - * Dummy NOP action for all statemachines - */ -static void fsm_action_nop(fsm_instance *fi, int event, void *arg) -{ -} - /* * Actions of the connection statemachine */ Index: linux-2.6-uschi/drivers/s390/net/ctcmain.c =================================================================== --- linux-2.6-uschi.orig/drivers/s390/net/ctcmain.c +++ linux-2.6-uschi/drivers/s390/net/ctcmain.c @@ -644,14 +644,6 @@ ctc_checkalloc_buffer(struct channel *ch } /** - * Dummy NOP action for statemachines - */ -static void -fsm_action_nop(fsm_instance * fi, int event, void *arg) -{ -} - -/** * Actions for channel - statemachines. *****************************************************************************/ -- ^ permalink raw reply [flat|nested] 8+ messages in thread
* [patch 2/4] drivers/s390/net: Kconfig brush up 2008-02-06 11:55 [patch 0/4] s390: ctc patches for 2.6.25 (2nd try) Ursula Braun 2008-02-06 11:55 ` [patch 1/4] ctc / netiucv: consolidate fsm_action_nop Ursula Braun @ 2008-02-06 11:55 ` Ursula Braun 2008-02-06 11:55 ` [patch 3/4] ctcm: infrastructure for replaced ctc driver Ursula Braun 2008-02-06 11:55 ` [patch 4/4] ctc: removal of the old " Ursula Braun 3 siblings, 0 replies; 8+ messages in thread From: Ursula Braun @ 2008-02-06 11:55 UTC (permalink / raw) To: jgarzik, netdev, linux-s390; +Cc: Peter Tiedemann [-- Attachment #1: 701-kconfig.diff --] [-- Type: text/plain, Size: 4356 bytes --] From: Peter Tiedemann <ptiedem@de.ibm.com> From: Ursula Braun <braunu@de.ibm.com> adapt drivers/s390/net/Kconfig to current IBM wording and further cosmetics Signed-off-by: Peter Tiedemann <ptiedem@de.ibm.com> Signed-off-by: Ursula Braun <braunu@de.ibm.com> --- drivers/s390/net/Kconfig | 43 ++++++++++++++++++++++--------------------- 1 file changed, 22 insertions(+), 21 deletions(-) Index: linux-2.6-uschi/drivers/s390/net/Kconfig =================================================================== --- linux-2.6-uschi.orig/drivers/s390/net/Kconfig +++ linux-2.6-uschi/drivers/s390/net/Kconfig @@ -5,22 +5,23 @@ config LCS tristate "Lan Channel Station Interface" depends on CCW && NETDEVICES && (NET_ETHERNET || TR || FDDI) help - Select this option if you want to use LCS networking on IBM S/390 - or zSeries. This device driver supports Token Ring (IEEE 802.5), - FDDI (IEEE 802.7) and Ethernet. - This option is also available as a module which will be - called lcs.ko. If you do not know what it is, it's safe to say "Y". + Select this option if you want to use LCS networking on IBM System z. + This device driver supports Token Ring (IEEE 802.5), + FDDI (IEEE 802.7) and Ethernet. + To compile as a module, choose M. The module name is lcs.ko. + If you do not know what it is, it's safe to choose Y. config CTC tristate "CTC device support" depends on CCW && NETDEVICES help - Select this option if you want to use channel-to-channel networking - on IBM S/390 or zSeries. This device driver supports real CTC - coupling using ESCON. It also supports virtual CTCs when running - under VM. It will use the channel device configuration if this is - available. This option is also available as a module which will be - called ctc.ko. If you do not know what it is, it's safe to say "Y". + Select this option if you want to use channel-to-channel + point-to-point networking on IBM System z. + This device driver supports real CTC coupling using ESCON. + It also supports virtual CTCs when running under VM. + To compile as a module, choose M. The module name is ctc.ko. + To compile into the kernel, choose Y. + If you do not need any channel-to-channel connection, choose N. config NETIUCV tristate "IUCV network device support (VM only)" @@ -29,9 +30,9 @@ config NETIUCV Select this option if you want to use inter-user communication vehicle networking under VM or VIF. It enables a fast communication link between VM guests. Using ifconfig a point-to-point connection - can be established to the Linux for zSeries and S7390 system - running on the other VM guest. This option is also available - as a module which will be called netiucv.ko. If unsure, say "Y". + can be established to the Linux on IBM System z + running on the other VM guest. To compile as a module, choose M. + The module name is netiucv.ko. If unsure, choose Y. config SMSGIUCV tristate "IUCV special message support (VM only)" @@ -47,22 +48,22 @@ config CLAW This driver supports channel attached CLAW devices. CLAW is Common Link Access for Workstation. Common devices that use CLAW are RS/6000s, Cisco Routers (CIP) and 3172 devices. - To compile as a module choose M here: The module will be called - claw.ko to compile into the kernel choose Y + To compile as a module, choose M. The module name is claw.ko. + To compile into the kernel, choose Y. config QETH tristate "Gigabit Ethernet device support" depends on CCW && NETDEVICES && IP_MULTICAST && QDIO help - This driver supports the IBM S/390 and zSeries OSA Express adapters + This driver supports the IBM System z OSA Express adapters in QDIO mode (all media types), HiperSockets interfaces and VM GuestLAN interfaces in QDIO and HIPER mode. - For details please refer to the documentation provided by IBM at - <http://www10.software.ibm.com/developerworks/opensource/linux390> + For details please refer to the documentation provided by IBM at + <http://www.ibm.com/developerworks/linux/linux390> - To compile this driver as a module, choose M here: the - module will be called qeth.ko. + To compile this driver as a module, choose M. + The module name is qeth.ko. comment "Gigabit Ethernet default settings" -- ^ permalink raw reply [flat|nested] 8+ messages in thread
* [patch 3/4] ctcm: infrastructure for replaced ctc driver 2008-02-06 11:55 [patch 0/4] s390: ctc patches for 2.6.25 (2nd try) Ursula Braun 2008-02-06 11:55 ` [patch 1/4] ctc / netiucv: consolidate fsm_action_nop Ursula Braun 2008-02-06 11:55 ` [patch 2/4] drivers/s390/net: Kconfig brush up Ursula Braun @ 2008-02-06 11:55 ` Ursula Braun 2008-02-06 11:55 ` [patch 4/4] ctc: removal of the old " Ursula Braun 3 siblings, 0 replies; 8+ messages in thread From: Ursula Braun @ 2008-02-06 11:55 UTC (permalink / raw) To: jgarzik, netdev, linux-s390; +Cc: Peter Tiedemann [-- Attachment #1: 702-ctcm-diff --] [-- Type: text/plain, Size: 235416 bytes --] From: Peter Tiedemann <ptiedem@de.ibm.com> establish base stuff for the replaced ctc driver, i.e. Kconfig and Makefile adaptions arch/s390/defconfig drivers/s390/net/Kconfig drivers/s390/net/Makefile Signed-off-by: Peter Tiedemann <ptiedem@de.ibm.com> Signed-off-by: Ursula Braun <braunu@de.ibm.com> --- drivers/s390/net/Kconfig | 12 drivers/s390/net/Makefile | 5 drivers/s390/net/ctcm_dbug.c | 67 + drivers/s390/net/ctcm_dbug.h | 158 ++ drivers/s390/net/ctcm_fsms.c | 2338 +++++++++++++++++++++++++++++++++++++++ drivers/s390/net/ctcm_fsms.h | 359 ++++++ drivers/s390/net/ctcm_main.c | 1772 ++++++++++++++++++++++++++++++ drivers/s390/net/ctcm_main.h | 287 ++++ drivers/s390/net/ctcm_mpc.c | 2467 ++++++++++++++++++++++++++++++++++++++++++ drivers/s390/net/ctcm_mpc.h | 239 ++++ drivers/s390/net/ctcm_sysfs.c | 210 +++ 11 files changed, 7906 insertions(+), 8 deletions(-) Index: linux-2.6-uschi/drivers/s390/net/Makefile =================================================================== --- linux-2.6-uschi.orig/drivers/s390/net/Makefile +++ linux-2.6-uschi/drivers/s390/net/Makefile @@ -2,11 +2,10 @@ # S/390 network devices # -ctc-objs := ctcmain.o ctcdbug.o - +ctcm-objs := ctcm_main.o ctcm_fsms.o ctcm_mpc.o ctcm_sysfs.o ctcm_dbug.o +obj-$(CONFIG_CTCM) += ctcm.o fsm.o cu3088.o obj-$(CONFIG_NETIUCV) += netiucv.o fsm.o obj-$(CONFIG_SMSGIUCV) += smsgiucv.o -obj-$(CONFIG_CTC) += ctc.o fsm.o cu3088.o obj-$(CONFIG_LCS) += lcs.o cu3088.o obj-$(CONFIG_CLAW) += claw.o cu3088.o qeth-y := qeth_main.o qeth_mpc.o qeth_sys.o qeth_eddp.o Index: linux-2.6-uschi/drivers/s390/net/Kconfig =================================================================== --- linux-2.6-uschi.orig/drivers/s390/net/Kconfig +++ linux-2.6-uschi/drivers/s390/net/Kconfig @@ -11,15 +11,17 @@ config LCS To compile as a module, choose M. The module name is lcs.ko. If you do not know what it is, it's safe to choose Y. -config CTC - tristate "CTC device support" +config CTCM + tristate "CTC and MPC SNA device support" depends on CCW && NETDEVICES help Select this option if you want to use channel-to-channel point-to-point networking on IBM System z. This device driver supports real CTC coupling using ESCON. It also supports virtual CTCs when running under VM. - To compile as a module, choose M. The module name is ctc.ko. + This driver also supports channel-to-channel MPC SNA devices. + MPC is an SNA protocol device used by Communication Server for Linux. + To compile as a module, choose M. The module name is ctcm.ko. To compile into the kernel, choose Y. If you do not need any channel-to-channel connection, choose N. @@ -84,7 +86,7 @@ config QETH_VLAN 802.1q VLAN support in the qeth device driver. config CCWGROUP - tristate - default (LCS || CTC || QETH) + tristate + default (LCS || CTCM || QETH) endmenu Index: linux-2.6-uschi/drivers/s390/net/ctcm_dbug.c =================================================================== --- /dev/null +++ linux-2.6-uschi/drivers/s390/net/ctcm_dbug.c @@ -0,0 +1,67 @@ +/* + * drivers/s390/net/ctcm_dbug.c + * + * Copyright IBM Corp. 2001, 2007 + * Authors: Peter Tiedemann (ptiedem@de.ibm.com) + * + */ + +#include <linux/stddef.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/slab.h> +#include <linux/ctype.h> +#include <linux/sysctl.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/fs.h> +#include <linux/debugfs.h> +#include "ctcm_dbug.h" + +/* + * Debug Facility Stuff + */ + +DEFINE_PER_CPU(char[256], ctcm_dbf_txt_buf); + +struct ctcm_dbf_info ctcm_dbf[CTCM_DBF_INFOS] = { + [CTCM_DBF_SETUP] = {"ctc_setup", 8, 1, 64, 5, NULL}, + [CTCM_DBF_ERROR] = {"ctc_error", 8, 1, 64, 3, NULL}, + [CTCM_DBF_TRACE] = {"ctc_trace", 8, 1, 64, 3, NULL}, + [CTCM_DBF_MPC_SETUP] = {"mpc_setup", 8, 1, 64, 5, NULL}, + [CTCM_DBF_MPC_ERROR] = {"mpc_error", 8, 1, 64, 3, NULL}, + [CTCM_DBF_MPC_TRACE] = {"mpc_trace", 8, 1, 64, 3, NULL}, +}; + +void ctcm_unregister_dbf_views(void) +{ + int x; + for (x = 0; x < CTCM_DBF_INFOS; x++) { + debug_unregister(ctcm_dbf[x].id); + ctcm_dbf[x].id = NULL; + } +} + +int ctcm_register_dbf_views(void) +{ + int x; + for (x = 0; x < CTCM_DBF_INFOS; x++) { + /* register the areas */ + ctcm_dbf[x].id = debug_register(ctcm_dbf[x].name, + ctcm_dbf[x].pages, + ctcm_dbf[x].areas, + ctcm_dbf[x].len); + if (ctcm_dbf[x].id == NULL) { + ctcm_unregister_dbf_views(); + return -ENOMEM; + } + + /* register a view */ + debug_register_view(ctcm_dbf[x].id, &debug_hex_ascii_view); + /* set a passing level */ + debug_set_level(ctcm_dbf[x].id, ctcm_dbf[x].level); + } + + return 0; +} + Index: linux-2.6-uschi/drivers/s390/net/ctcm_dbug.h =================================================================== --- /dev/null +++ linux-2.6-uschi/drivers/s390/net/ctcm_dbug.h @@ -0,0 +1,158 @@ +/* + * drivers/s390/net/ctcm_dbug.h + * + * Copyright IBM Corp. 2001, 2007 + * Authors: Peter Tiedemann (ptiedem@de.ibm.com) + * + */ + +#ifndef _CTCM_DBUG_H_ +#define _CTCM_DBUG_H_ + +/* + * Debug Facility stuff + */ + +#include <asm/debug.h> + +#ifdef DEBUG + #define do_debug 1 +#else + #define do_debug 0 +#endif +#ifdef DEBUGDATA + #define do_debug_data 1 +#else + #define do_debug_data 0 +#endif +#ifdef DEBUGCCW + #define do_debug_ccw 1 +#else + #define do_debug_ccw 0 +#endif + +/* define dbf debug levels similar to kernel msg levels */ +#define CTC_DBF_ALWAYS 0 /* always print this */ +#define CTC_DBF_EMERG 0 /* system is unusable */ +#define CTC_DBF_ALERT 1 /* action must be taken immediately */ +#define CTC_DBF_CRIT 2 /* critical conditions */ +#define CTC_DBF_ERROR 3 /* error conditions */ +#define CTC_DBF_WARN 4 /* warning conditions */ +#define CTC_DBF_NOTICE 5 /* normal but significant condition */ +#define CTC_DBF_INFO 5 /* informational */ +#define CTC_DBF_DEBUG 6 /* debug-level messages */ + +DECLARE_PER_CPU(char[256], ctcm_dbf_txt_buf); + +enum ctcm_dbf_names { + CTCM_DBF_SETUP, + CTCM_DBF_ERROR, + CTCM_DBF_TRACE, + CTCM_DBF_MPC_SETUP, + CTCM_DBF_MPC_ERROR, + CTCM_DBF_MPC_TRACE, + CTCM_DBF_INFOS /* must be last element */ +}; + +struct ctcm_dbf_info { + char name[DEBUG_MAX_NAME_LEN]; + int pages; + int areas; + int len; + int level; + debug_info_t *id; +}; + +extern struct ctcm_dbf_info ctcm_dbf[CTCM_DBF_INFOS]; + +int ctcm_register_dbf_views(void); +void ctcm_unregister_dbf_views(void); + +static inline const char *strtail(const char *s, int n) +{ + int l = strlen(s); + return (l > n) ? s + (l - n) : s; +} + +/* sort out levels early to avoid unnecessary sprintfs */ +static inline int ctcm_dbf_passes(debug_info_t *dbf_grp, int level) +{ + return (dbf_grp->level >= level); +} + +#define CTCM_FUNTAIL strtail((char *)__func__, 16) + +#define CTCM_DBF_TEXT(name, level, text) \ + do { \ + debug_text_event(ctcm_dbf[CTCM_DBF_##name].id, level, text); \ + } while (0) + +#define CTCM_DBF_HEX(name, level, addr, len) \ + do { \ + debug_event(ctcm_dbf[CTCM_DBF_##name].id, \ + level, (void *)(addr), len); \ + } while (0) + +#define CTCM_DBF_TEXT_(name, level, text...) \ + do { \ + if (ctcm_dbf_passes(ctcm_dbf[CTCM_DBF_##name].id, level)) { \ + char *ctcm_dbf_txt_buf = \ + get_cpu_var(ctcm_dbf_txt_buf); \ + sprintf(ctcm_dbf_txt_buf, text); \ + debug_text_event(ctcm_dbf[CTCM_DBF_##name].id, \ + level, ctcm_dbf_txt_buf); \ + put_cpu_var(ctcm_dbf_txt_buf); \ + } \ + } while (0) + +/* + * cat : one of {setup, mpc_setup, trace, mpc_trace, error, mpc_error}. + * dev : netdevice with valid name field. + * text: any text string. + */ +#define CTCM_DBF_DEV_NAME(cat, dev, text) \ + do { \ + CTCM_DBF_TEXT_(cat, CTC_DBF_INFO, "%s(%s) : %s", \ + CTCM_FUNTAIL, dev->name, text); \ + } while (0) + +#define MPC_DBF_DEV_NAME(cat, dev, text) \ + do { \ + CTCM_DBF_TEXT_(MPC_##cat, CTC_DBF_INFO, "%s(%s) : %s", \ + CTCM_FUNTAIL, dev->name, text); \ + } while (0) + +#define CTCMY_DBF_DEV_NAME(cat, dev, text) \ + do { \ + if (IS_MPCDEV(dev)) \ + MPC_DBF_DEV_NAME(cat, dev, text); \ + else \ + CTCM_DBF_DEV_NAME(cat, dev, text); \ + } while (0) + +/* + * cat : one of {setup, mpc_setup, trace, mpc_trace, error, mpc_error}. + * dev : netdevice. + * text: any text string. + */ +#define CTCM_DBF_DEV(cat, dev, text) \ + do { \ + CTCM_DBF_TEXT_(cat, CTC_DBF_INFO, "%s(%p) : %s", \ + CTCM_FUNTAIL, dev, text); \ + } while (0) + +#define MPC_DBF_DEV(cat, dev, text) \ + do { \ + CTCM_DBF_TEXT_(MPC_##cat, CTC_DBF_INFO, "%s(%p) : %s", \ + CTCM_FUNTAIL, dev, text); \ + } while (0) + +#define CTCMY_DBF_DEV(cat, dev, text) \ + do { \ + if (IS_MPCDEV(dev)) \ + MPC_DBF_DEV(cat, dev, text); \ + else \ + CTCM_DBF_DEV(cat, dev, text); \ + } while (0) + +#endif Index: linux-2.6-uschi/drivers/s390/net/ctcm_fsms.c =================================================================== --- /dev/null +++ linux-2.6-uschi/drivers/s390/net/ctcm_fsms.c @@ -0,0 +1,2338 @@ +/* + * drivers/s390/net/ctcm_fsms.c + * + * Copyright IBM Corp. 2001, 2007 + * Authors: Fritz Elfert (felfert@millenux.com) + * Peter Tiedemann (ptiedem@de.ibm.com) + * MPC additions : + * Belinda Thompson (belindat@us.ibm.com) + * Andy Richter (richtera@us.ibm.com) + */ + +#undef DEBUG +#undef DEBUGDATA +#undef DEBUGCCW + +#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/bitops.h> + +#include <linux/signal.h> +#include <linux/string.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 <linux/io.h> +#include <asm/ccwdev.h> +#include <asm/ccwgroup.h> +#include <linux/uaccess.h> + +#include <asm/idals.h> + +#include "fsm.h" +#include "cu3088.h" + +#include "ctcm_dbug.h" +#include "ctcm_main.h" +#include "ctcm_fsms.h" + +const char *dev_state_names[] = { + [DEV_STATE_STOPPED] = "Stopped", + [DEV_STATE_STARTWAIT_RXTX] = "StartWait RXTX", + [DEV_STATE_STARTWAIT_RX] = "StartWait RX", + [DEV_STATE_STARTWAIT_TX] = "StartWait TX", + [DEV_STATE_STOPWAIT_RXTX] = "StopWait RXTX", + [DEV_STATE_STOPWAIT_RX] = "StopWait RX", + [DEV_STATE_STOPWAIT_TX] = "StopWait TX", + [DEV_STATE_RUNNING] = "Running", +}; + +const char *dev_event_names[] = { + [DEV_EVENT_START] = "Start", + [DEV_EVENT_STOP] = "Stop", + [DEV_EVENT_RXUP] = "RX up", + [DEV_EVENT_TXUP] = "TX up", + [DEV_EVENT_RXDOWN] = "RX down", + [DEV_EVENT_TXDOWN] = "TX down", + [DEV_EVENT_RESTART] = "Restart", +}; + +const char *ctc_ch_event_names[] = { + [CTC_EVENT_IO_SUCCESS] = "ccw_device success", + [CTC_EVENT_IO_EBUSY] = "ccw_device busy", + [CTC_EVENT_IO_ENODEV] = "ccw_device enodev", + [CTC_EVENT_IO_UNKNOWN] = "ccw_device unknown", + [CTC_EVENT_ATTNBUSY] = "Status ATTN & BUSY", + [CTC_EVENT_ATTN] = "Status ATTN", + [CTC_EVENT_BUSY] = "Status BUSY", + [CTC_EVENT_UC_RCRESET] = "Unit check remote reset", + [CTC_EVENT_UC_RSRESET] = "Unit check remote system reset", + [CTC_EVENT_UC_TXTIMEOUT] = "Unit check TX timeout", + [CTC_EVENT_UC_TXPARITY] = "Unit check TX parity", + [CTC_EVENT_UC_HWFAIL] = "Unit check Hardware failure", + [CTC_EVENT_UC_RXPARITY] = "Unit check RX parity", + [CTC_EVENT_UC_ZERO] = "Unit check ZERO", + [CTC_EVENT_UC_UNKNOWN] = "Unit check Unknown", + [CTC_EVENT_SC_UNKNOWN] = "SubChannel check Unknown", + [CTC_EVENT_MC_FAIL] = "Machine check failure", + [CTC_EVENT_MC_GOOD] = "Machine check operational", + [CTC_EVENT_IRQ] = "IRQ normal", + [CTC_EVENT_FINSTAT] = "IRQ final", + [CTC_EVENT_TIMER] = "Timer", + [CTC_EVENT_START] = "Start", + [CTC_EVENT_STOP] = "Stop", + /* + * additional MPC events + */ + [CTC_EVENT_SEND_XID] = "XID Exchange", + [CTC_EVENT_RSWEEP_TIMER] = "MPC Group Sweep Timer", +}; + +const char *ctc_ch_state_names[] = { + [CTC_STATE_IDLE] = "Idle", + [CTC_STATE_STOPPED] = "Stopped", + [CTC_STATE_STARTWAIT] = "StartWait", + [CTC_STATE_STARTRETRY] = "StartRetry", + [CTC_STATE_SETUPWAIT] = "SetupWait", + [CTC_STATE_RXINIT] = "RX init", + [CTC_STATE_TXINIT] = "TX init", + [CTC_STATE_RX] = "RX", + [CTC_STATE_TX] = "TX", + [CTC_STATE_RXIDLE] = "RX idle", + [CTC_STATE_TXIDLE] = "TX idle", + [CTC_STATE_RXERR] = "RX error", + [CTC_STATE_TXERR] = "TX error", + [CTC_STATE_TERM] = "Terminating", + [CTC_STATE_DTERM] = "Restarting", + [CTC_STATE_NOTOP] = "Not operational", + /* + * additional MPC states + */ + [CH_XID0_PENDING] = "Pending XID0 Start", + [CH_XID0_INPROGRESS] = "In XID0 Negotiations ", + [CH_XID7_PENDING] = "Pending XID7 P1 Start", + [CH_XID7_PENDING1] = "Active XID7 P1 Exchange ", + [CH_XID7_PENDING2] = "Pending XID7 P2 Start ", + [CH_XID7_PENDING3] = "Active XID7 P2 Exchange ", + [CH_XID7_PENDING4] = "XID7 Complete - Pending READY ", +}; + +/* + * ----- static ctcm actions for channel statemachine ----- + * +*/ +static void chx_txdone(fsm_instance *fi, int event, void *arg); +static void chx_rx(fsm_instance *fi, int event, void *arg); +static void chx_rxidle(fsm_instance *fi, int event, void *arg); +static void chx_firstio(fsm_instance *fi, int event, void *arg); +static void ctcm_chx_setmode(fsm_instance *fi, int event, void *arg); +static void ctcm_chx_start(fsm_instance *fi, int event, void *arg); +static void ctcm_chx_haltio(fsm_instance *fi, int event, void *arg); +static void ctcm_chx_stopped(fsm_instance *fi, int event, void *arg); +static void ctcm_chx_stop(fsm_instance *fi, int event, void *arg); +static void ctcm_chx_fail(fsm_instance *fi, int event, void *arg); +static void ctcm_chx_setuperr(fsm_instance *fi, int event, void *arg); +static void ctcm_chx_restart(fsm_instance *fi, int event, void *arg); +static void ctcm_chx_rxiniterr(fsm_instance *fi, int event, void *arg); +static void ctcm_chx_rxinitfail(fsm_instance *fi, int event, void *arg); +static void ctcm_chx_rxdisc(fsm_instance *fi, int event, void *arg); +static void ctcm_chx_txiniterr(fsm_instance *fi, int event, void *arg); +static void ctcm_chx_txretry(fsm_instance *fi, int event, void *arg); +static void ctcm_chx_iofatal(fsm_instance *fi, int event, void *arg); + +/* + * ----- static ctcmpc actions for ctcmpc channel statemachine ----- + * +*/ +static void ctcmpc_chx_txdone(fsm_instance *fi, int event, void *arg); +static void ctcmpc_chx_rx(fsm_instance *fi, int event, void *arg); +static void ctcmpc_chx_firstio(fsm_instance *fi, int event, void *arg); +/* shared : +static void ctcm_chx_setmode(fsm_instance *fi, int event, void *arg); +static void ctcm_chx_start(fsm_instance *fi, int event, void *arg); +static void ctcm_chx_haltio(fsm_instance *fi, int event, void *arg); +static void ctcm_chx_stopped(fsm_instance *fi, int event, void *arg); +static void ctcm_chx_stop(fsm_instance *fi, int event, void *arg); +static void ctcm_chx_fail(fsm_instance *fi, int event, void *arg); +static void ctcm_chx_setuperr(fsm_instance *fi, int event, void *arg); +static void ctcm_chx_restart(fsm_instance *fi, int event, void *arg); +static void ctcm_chx_rxiniterr(fsm_instance *fi, int event, void *arg); +static void ctcm_chx_rxinitfail(fsm_instance *fi, int event, void *arg); +static void ctcm_chx_rxdisc(fsm_instance *fi, int event, void *arg); +static void ctcm_chx_txiniterr(fsm_instance *fi, int event, void *arg); +static void ctcm_chx_txretry(fsm_instance *fi, int event, void *arg); +static void ctcm_chx_iofatal(fsm_instance *fi, int event, void *arg); +*/ +static void ctcmpc_chx_attn(fsm_instance *fsm, int event, void *arg); +static void ctcmpc_chx_attnbusy(fsm_instance *, int, void *); +static void ctcmpc_chx_resend(fsm_instance *, int, void *); +static void ctcmpc_chx_send_sweep(fsm_instance *fsm, int event, void *arg); + +/** + * Check return code of a preceeding ccw_device call, halt_IO etc... + * + * @param ch The channel, the error belongs to. + * @param return_code The error code (!= 0) to inspect. + */ +void ctcm_ccw_check_rc(struct channel *ch, int rc, char *msg) +{ + CTCM_DBF_TEXT_(ERROR, CTC_DBF_ERROR, + "ccw error %s (%s): %04x\n", ch->id, msg, rc); + switch (rc) { + case -EBUSY: + ctcm_pr_warn("%s (%s): Busy !\n", ch->id, msg); + fsm_event(ch->fsm, CTC_EVENT_IO_EBUSY, ch); + break; + case -ENODEV: + ctcm_pr_emerg("%s (%s): Invalid device called for IO\n", + ch->id, msg); + fsm_event(ch->fsm, CTC_EVENT_IO_ENODEV, ch); + break; + default: + ctcm_pr_emerg("%s (%s): Unknown error in do_IO %04x\n", + ch->id, msg, rc); + fsm_event(ch->fsm, CTC_EVENT_IO_UNKNOWN, ch); + } +} + +void ctcm_purge_skb_queue(struct sk_buff_head *q) +{ + struct sk_buff *skb; + + CTCM_DBF_TEXT(TRACE, 3, __FUNCTION__); + + while ((skb = skb_dequeue(q))) { + atomic_dec(&skb->users); + dev_kfree_skb_any(skb); + } +} + +/* + * 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 chx_txdone(fsm_instance *fi, int event, void *arg) +{ + struct channel *ch = (struct channel *)arg; + struct net_device *dev = ch->netdev; + struct ctcm_priv *privptr = dev->priv; + struct sk_buff *skb; + int first = 1; + int i; + unsigned long duration; + struct timespec done_stamp = current_kernel_time(); /* xtime */ + + duration = + (done_stamp.tv_sec - ch->prof.send_stamp.tv_sec) * 1000000 + + (done_stamp.tv_nsec - ch->prof.send_stamp.tv_nsec) / 1000; + if (duration > ch->prof.tx_time) + ch->prof.tx_time = duration; + + if (ch->irb->scsw.count != 0) + ctcm_pr_debug("%s: TX not complete, remaining %d bytes\n", + dev->name, ch->irb->scsw.count); + 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 (ctcm_checkalloc_buffer(ch)) { + spin_unlock(&ch->collect_lock); + return; + } + ch->trans_skb->data = ch->trans_skb_data; + skb_reset_tail_pointer(ch->trans_skb); + 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); + *((__u16 *)skb_put(ch->trans_skb, 2)) = ch->collect_len + 2; + i = 0; + while ((skb = skb_dequeue(&ch->collect_queue))) { + skb_copy_from_linear_data(skb, + skb_put(ch->trans_skb, skb->len), 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; + spin_unlock(&ch->collect_lock); + ch->ccw[1].count = ch->trans_skb->len; + fsm_addtimer(&ch->timer, CTCM_TIME_5_SEC, CTC_EVENT_TIMER, ch); + ch->prof.send_stamp = current_kernel_time(); /* xtime */ + rc = ccw_device_start(ch->cdev, &ch->ccw[0], + (unsigned long)ch, 0xff, 0); + ch->prof.doios_multi++; + if (rc != 0) { + privptr->stats.tx_dropped += i; + privptr->stats.tx_errors += i; + fsm_deltimer(&ch->timer); + ctcm_ccw_check_rc(ch, rc, "chained TX"); + } + } else { + spin_unlock(&ch->collect_lock); + fsm_newstate(fi, CTC_STATE_TXIDLE); + } + ctcm_clear_busy_do(dev); +} + +/** + * 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. + */ +void ctcm_chx_txidle(fsm_instance *fi, int event, void *arg) +{ + struct channel *ch = (struct channel *)arg; + + CTCM_DBF_TEXT(TRACE, 6, __FUNCTION__); + fsm_deltimer(&ch->timer); + fsm_newstate(fi, CTC_STATE_TXIDLE); + fsm_event(((struct ctcm_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 chx_rx(fsm_instance *fi, int event, void *arg) +{ + struct channel *ch = (struct channel *)arg; + struct net_device *dev = ch->netdev; + struct ctcm_priv *privptr = dev->priv; + int len = ch->max_bufsize - ch->irb->scsw.count; + 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) { + ctcm_pr_debug("%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) { + ctcm_pr_debug("%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 CTCM_PROTO_S390: + case CTCM_PROTO_OS390: + check_len = block_len + 2; + break; + default: + check_len = block_len; + break; + } + if ((len < block_len) || (len > check_len)) { + ctcm_pr_debug("%s: got block length %d != rx length %d\n", + dev->name, block_len, len); + if (do_debug) + ctcmpc_dump_skb(skb, 0); + + *((__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; + ctcm_unpack_skb(ch, skb); + } + again: + skb->data = ch->trans_skb_data; + skb_reset_tail_pointer(skb); + skb->len = 0; + if (ctcm_checkalloc_buffer(ch)) + return; + ch->ccw[1].count = ch->max_bufsize; + rc = ccw_device_start(ch->cdev, &ch->ccw[0], + (unsigned long)ch, 0xff, 0); + if (rc != 0) + ctcm_ccw_check_rc(ch, rc, "normal RX"); +} + +static void chx_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 chx_firstio(fsm_instance *fi, int event, void *arg) +{ + struct channel *ch = (struct channel *)arg; + int rc; + + CTCM_DBF_TEXT(TRACE, 6, __FUNCTION__); + + if (fsm_getstate(fi) == CTC_STATE_TXIDLE) + ctcm_pr_debug("%s: remote side issued READ?, init.\n", ch->id); + fsm_deltimer(&ch->timer); + if (ctcm_checkalloc_buffer(ch)) + return; + if ((fsm_getstate(fi) == CTC_STATE_SETUPWAIT) && + (ch->protocol == CTCM_PROTO_OS390)) { + /* OS/390 resp. z/OS */ + if (CHANNEL_DIRECTION(ch->flags) == READ) { + *((__u16 *)ch->trans_skb->data) = CTCM_INITIAL_BLOCKLEN; + fsm_addtimer(&ch->timer, CTCM_TIME_5_SEC, + CTC_EVENT_TIMER, ch); + chx_rxidle(fi, event, arg); + } else { + struct net_device *dev = ch->netdev; + fsm_newstate(fi, CTC_STATE_TXIDLE); + fsm_event(((struct ctcm_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 != CTCM_PROTO_S390)) + fsm_addtimer(&ch->timer, CTCM_TIME_5_SEC, CTC_EVENT_TIMER, ch); + + *((__u16 *)ch->trans_skb->data) = CTCM_INITIAL_BLOCKLEN; + ch->ccw[1].count = 2; /* Transfer only length */ + + fsm_newstate(fi, (CHANNEL_DIRECTION(ch->flags) == READ) + ? CTC_STATE_RXINIT : CTC_STATE_TXINIT); + rc = ccw_device_start(ch->cdev, &ch->ccw[0], + (unsigned long)ch, 0xff, 0); + if (rc != 0) { + fsm_deltimer(&ch->timer); + fsm_newstate(fi, CTC_STATE_SETUPWAIT); + ctcm_ccw_check_rc(ch, rc, "init IO"); + } + /* + * 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 == CTCM_PROTO_S390)) { + struct net_device *dev = ch->netdev; + fsm_event(((struct ctcm_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 chx_rxidle(fsm_instance *fi, int event, void *arg) +{ + struct channel *ch = (struct channel *)arg; + struct net_device *dev = ch->netdev; + __u16 buflen; + int rc; + + CTCM_DBF_TEXT(TRACE, 6, __FUNCTION__); + fsm_deltimer(&ch->timer); + buflen = *((__u16 *)ch->trans_skb->data); + if (do_debug) + ctcm_pr_debug("%s: Initial RX count %d\n", dev->name, buflen); + + if (buflen >= CTCM_INITIAL_BLOCKLEN) { + if (ctcm_checkalloc_buffer(ch)) + return; + ch->ccw[1].count = ch->max_bufsize; + fsm_newstate(fi, CTC_STATE_RXIDLE); + rc = ccw_device_start(ch->cdev, &ch->ccw[0], + (unsigned long)ch, 0xff, 0); + if (rc != 0) { + fsm_newstate(fi, CTC_STATE_RXINIT); + ctcm_ccw_check_rc(ch, rc, "initial RX"); + } else + fsm_event(((struct ctcm_priv *)dev->priv)->fsm, + DEV_EVENT_RXUP, dev); + } else { + if (do_debug) + ctcm_pr_debug("%s: Initial RX count %d not %d\n", + dev->name, buflen, CTCM_INITIAL_BLOCKLEN); + chx_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 ctcm_chx_setmode(fsm_instance *fi, int event, void *arg) +{ + struct channel *ch = (struct channel *)arg; + int rc; + unsigned long saveflags = 0; + int timeout = CTCM_TIME_5_SEC; + + fsm_deltimer(&ch->timer); + if (IS_MPC(ch)) { + timeout = 1500; + if (do_debug) + ctcm_pr_debug("ctcm enter: %s(): cp=%i ch=0x%p id=%s\n", + __FUNCTION__, smp_processor_id(), ch, ch->id); + } + fsm_addtimer(&ch->timer, timeout, CTC_EVENT_TIMER, ch); + fsm_newstate(fi, CTC_STATE_SETUPWAIT); + if (do_debug_ccw && IS_MPC(ch)) + ctcmpc_dumpit((char *)&ch->ccw[6], sizeof(struct ccw1) * 2); + + if (event == CTC_EVENT_TIMER) /* only for timer not yet locked */ + spin_lock_irqsave(get_ccwdev_lock(ch->cdev), saveflags); + /* Such conditional locking is undeterministic in + * static view. => ignore sparse warnings here. */ + + rc = ccw_device_start(ch->cdev, &ch->ccw[6], + (unsigned long)ch, 0xff, 0); + if (event == CTC_EVENT_TIMER) /* see above comments */ + spin_unlock_irqrestore(get_ccwdev_lock(ch->cdev), saveflags); + if (rc != 0) { + fsm_deltimer(&ch->timer); + fsm_newstate(fi, CTC_STATE_STARTWAIT); + ctcm_ccw_check_rc(ch, rc, "set Mode"); + } 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 ctcm_chx_start(fsm_instance *fi, int event, void *arg) +{ + struct channel *ch = (struct channel *)arg; + int rc; + struct net_device *dev; + unsigned long saveflags; + + CTCM_DBF_TEXT(TRACE, 5, __FUNCTION__); + if (ch == NULL) { + ctcm_pr_warn("chx_start ch=NULL\n"); + return; + } + if (ch->netdev == NULL) { + ctcm_pr_warn("chx_start dev=NULL, id=%s\n", ch->id); + return; + } + dev = ch->netdev; + + if (do_debug) + ctcm_pr_debug("%s: %s channel start\n", dev->name, + (CHANNEL_DIRECTION(ch->flags) == READ) ? "RX" : "TX"); + + 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 (ctcm_checkalloc_buffer(ch)) { + ctcm_pr_notice("%s: %s trans_skb allocation delayed " + "until first transfer\n", dev->name, + (CHANNEL_DIRECTION(ch->flags) == READ) ? "RX" : "TX"); + } + + 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(struct ccw1) * 3); + ch->ccw[4].cda = 0; + ch->ccw[4].flags &= ~CCW_FLAG_IDA; + + fsm_newstate(fi, CTC_STATE_STARTWAIT); + fsm_addtimer(&ch->timer, 1000, CTC_EVENT_TIMER, ch); + spin_lock_irqsave(get_ccwdev_lock(ch->cdev), saveflags); + rc = ccw_device_halt(ch->cdev, (unsigned long)ch); + spin_unlock_irqrestore(get_ccwdev_lock(ch->cdev), saveflags); + if (rc != 0) { + if (rc != -EBUSY) + fsm_deltimer(&ch->timer); + ctcm_ccw_check_rc(ch, rc, "initial HaltIO"); + } +} + +/** + * 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 ctcm_chx_haltio(fsm_instance *fi, int event, void *arg) +{ + struct channel *ch = (struct channel *)arg; + unsigned long saveflags = 0; + int rc; + int oldstate; + + CTCM_DBF_TEXT(TRACE, 2, __FUNCTION__); + fsm_deltimer(&ch->timer); + if (IS_MPC(ch)) + fsm_deltimer(&ch->sweep_timer); + + fsm_addtimer(&ch->timer, CTCM_TIME_5_SEC, CTC_EVENT_TIMER, ch); + + if (event == CTC_EVENT_STOP) /* only for STOP not yet locked */ + spin_lock_irqsave(get_ccwdev_lock(ch->cdev), saveflags); + /* Such conditional locking is undeterministic in + * static view. => ignore sparse warnings here. */ + oldstate = fsm_getstate(fi); + fsm_newstate(fi, CTC_STATE_TERM); + rc = ccw_device_halt(ch->cdev, (unsigned long)ch); + + if (event == CTC_EVENT_STOP) + spin_unlock_irqrestore(get_ccwdev_lock(ch->cdev), saveflags); + /* see remark above about conditional locking */ + + if (rc != 0 && rc != -EBUSY) { + fsm_deltimer(&ch->timer); + if (event != CTC_EVENT_STOP) { + fsm_newstate(fi, oldstate); + ctcm_ccw_check_rc(ch, rc, (char *)__FUNCTION__); + } + } +} + +/** + * Cleanup helper for chx_fail and chx_stopped + * cleanup channels queue and notify interface statemachine. + * + * @param fi An instance of a channel statemachine. + * @param state The next state (depending on caller). + * @param ch The channel to operate on. + */ +static void ctcm_chx_cleanup(fsm_instance *fi, int state, + struct channel *ch) +{ + struct net_device *dev = ch->netdev; + + CTCM_DBF_TEXT(TRACE, 3, __FUNCTION__); + + fsm_deltimer(&ch->timer); + if (IS_MPC(ch)) + fsm_deltimer(&ch->sweep_timer); + + fsm_newstate(fi, state); + if (state == CTC_STATE_STOPPED && ch->trans_skb != NULL) { + clear_normalized_cda(&ch->ccw[1]); + dev_kfree_skb_any(ch->trans_skb); + ch->trans_skb = NULL; + } + + ch->th_seg = 0x00; + ch->th_seq_num = 0x00; + if (CHANNEL_DIRECTION(ch->flags) == READ) { + skb_queue_purge(&ch->io_queue); + fsm_event(((struct ctcm_priv *)dev->priv)->fsm, + DEV_EVENT_RXDOWN, dev); + } else { + ctcm_purge_skb_queue(&ch->io_queue); + if (IS_MPC(ch)) + ctcm_purge_skb_queue(&ch->sweep_queue); + spin_lock(&ch->collect_lock); + ctcm_purge_skb_queue(&ch->collect_queue); + ch->collect_len = 0; + spin_unlock(&ch->collect_lock); + fsm_event(((struct ctcm_priv *)dev->priv)->fsm, + DEV_EVENT_TXDOWN, dev); + } +} + +/** + * 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 ctcm_chx_stopped(fsm_instance *fi, int event, void *arg) +{ + CTCM_DBF_TEXT(TRACE, 3, __FUNCTION__); + ctcm_chx_cleanup(fi, CTC_STATE_STOPPED, (struct channel *)arg); +} + +/** + * 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 ctcm_chx_stop(fsm_instance *fi, int event, void *arg) +{ + fsm_newstate(fi, CTC_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 ctcm_chx_fail(fsm_instance *fi, int event, void *arg) +{ + CTCM_DBF_TEXT(TRACE, 3, __FUNCTION__); + ctcm_chx_cleanup(fi, CTC_STATE_NOTOP, (struct channel *)arg); +} + +/** + * 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 ctcm_chx_setuperr(fsm_instance *fi, int event, void *arg) +{ + struct channel *ch = (struct channel *)arg; + struct net_device *dev = ch->netdev; + struct ctcm_priv *priv = (struct ctcm_priv *)dev->priv; + + /* + * 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) == CTC_STATE_SETUPWAIT) && + ((event == CTC_EVENT_UC_RCRESET) || + (event == CTC_EVENT_UC_RSRESET))) { + fsm_newstate(fi, CTC_STATE_STARTRETRY); + fsm_deltimer(&ch->timer); + fsm_addtimer(&ch->timer, CTCM_TIME_5_SEC, CTC_EVENT_TIMER, ch); + if (!IS_MPC(ch) && (CHANNEL_DIRECTION(ch->flags) == READ)) { + int rc = ccw_device_halt(ch->cdev, (unsigned long)ch); + if (rc != 0) + ctcm_ccw_check_rc(ch, rc, + "HaltIO in chx_setuperr"); + } + return; + } + + CTCM_DBF_TEXT_(ERROR, CTC_DBF_CRIT, + "%s : %s error during %s channel setup state=%s\n", + dev->name, ctc_ch_event_names[event], + (CHANNEL_DIRECTION(ch->flags) == READ) ? "RX" : "TX", + fsm_getstate_str(fi)); + + if (CHANNEL_DIRECTION(ch->flags) == READ) { + fsm_newstate(fi, CTC_STATE_RXERR); + fsm_event(priv->fsm, DEV_EVENT_RXDOWN, dev); + } else { + fsm_newstate(fi, CTC_STATE_TXERR); + fsm_event(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 ctcm_chx_restart(fsm_instance *fi, int event, void *arg) +{ + struct channel *ch = (struct channel *)arg; + struct net_device *dev = ch->netdev; + unsigned long saveflags = 0; + int oldstate; + int rc; + + CTCM_DBF_TEXT(TRACE, CTC_DBF_NOTICE, __FUNCTION__); + fsm_deltimer(&ch->timer); + ctcm_pr_debug("%s: %s channel restart\n", dev->name, + (CHANNEL_DIRECTION(ch->flags) == READ) ? "RX" : "TX"); + fsm_addtimer(&ch->timer, CTCM_TIME_5_SEC, CTC_EVENT_TIMER, ch); + oldstate = fsm_getstate(fi); + fsm_newstate(fi, CTC_STATE_STARTWAIT); + if (event == CTC_EVENT_TIMER) /* only for timer not yet locked */ + spin_lock_irqsave(get_ccwdev_lock(ch->cdev), saveflags); + /* Such conditional locking is a known problem for + * sparse because its undeterministic in static view. + * Warnings should be ignored here. */ + rc = ccw_device_halt(ch->cdev, (unsigned long)ch); + if (event == CTC_EVENT_TIMER) + spin_unlock_irqrestore(get_ccwdev_lock(ch->cdev), saveflags); + if (rc != 0) { + if (rc != -EBUSY) { + fsm_deltimer(&ch->timer); + fsm_newstate(fi, oldstate); + } + ctcm_ccw_check_rc(ch, rc, "HaltIO in ctcm_chx_restart"); + } +} + +/** + * 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 ctcm_chx_rxiniterr(fsm_instance *fi, int event, void *arg) +{ + struct channel *ch = (struct channel *)arg; + struct net_device *dev = ch->netdev; + + CTCM_DBF_TEXT(SETUP, 3, __FUNCTION__); + if (event == CTC_EVENT_TIMER) { + if (!IS_MPCDEV(dev)) + /* TODO : check if MPC deletes timer somewhere */ + fsm_deltimer(&ch->timer); + ctcm_pr_debug("%s: Timeout during RX init handshake\n", + dev->name); + if (ch->retry++ < 3) + ctcm_chx_restart(fi, event, arg); + else { + fsm_newstate(fi, CTC_STATE_RXERR); + fsm_event(((struct ctcm_priv *)dev->priv)->fsm, + DEV_EVENT_RXDOWN, dev); + } + } else + ctcm_pr_warn("%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 ctcm_chx_rxinitfail(fsm_instance *fi, int event, void *arg) +{ + struct channel *ch = (struct channel *)arg; + struct net_device *dev = ch->netdev; + + CTCM_DBF_TEXT(SETUP, 3, __FUNCTION__); + fsm_newstate(fi, CTC_STATE_RXERR); + ctcm_pr_warn("%s: RX busy. Initialization failed\n", dev->name); + fsm_event(((struct ctcm_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 ctcm_chx_rxdisc(fsm_instance *fi, int event, void *arg) +{ + struct channel *ch = (struct channel *)arg; + struct channel *ch2; + struct net_device *dev = ch->netdev; + + CTCM_DBF_DEV_NAME(TRACE, dev, "Got remote disconnect, re-initializing"); + fsm_deltimer(&ch->timer); + if (do_debug) + ctcm_pr_debug("%s: Got remote disconnect, " + "re-initializing ...\n", dev->name); + /* + * Notify device statemachine + */ + fsm_event(((struct ctcm_priv *)dev->priv)->fsm, DEV_EVENT_RXDOWN, dev); + fsm_event(((struct ctcm_priv *)dev->priv)->fsm, DEV_EVENT_TXDOWN, dev); + + fsm_newstate(fi, CTC_STATE_DTERM); + ch2 = ((struct ctcm_priv *)dev->priv)->channel[WRITE]; + fsm_newstate(ch2->fsm, CTC_STATE_DTERM); + + ccw_device_halt(ch->cdev, (unsigned long)ch); + ccw_device_halt(ch2->cdev, (unsigned long)ch2); +} + +/** + * 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 ctcm_chx_txiniterr(fsm_instance *fi, int event, void *arg) +{ + struct channel *ch = (struct channel *)arg; + struct net_device *dev = ch->netdev; + + if (event == CTC_EVENT_TIMER) { + fsm_deltimer(&ch->timer); + CTCM_DBF_DEV_NAME(ERROR, dev, + "Timeout during TX init handshake"); + if (ch->retry++ < 3) + ctcm_chx_restart(fi, event, arg); + else { + fsm_newstate(fi, CTC_STATE_TXERR); + fsm_event(((struct ctcm_priv *)dev->priv)->fsm, + DEV_EVENT_TXDOWN, dev); + } + } else { + CTCM_DBF_TEXT_(ERROR, CTC_DBF_ERROR, + "%s : %s error during channel setup state=%s", + dev->name, ctc_ch_event_names[event], + fsm_getstate_str(fi)); + + ctcm_pr_warn("%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 ctcm_chx_txretry(fsm_instance *fi, int event, void *arg) +{ + struct channel *ch = (struct channel *)arg; + struct net_device *dev = ch->netdev; + struct ctcm_priv *priv = (struct ctcm_priv *)dev->priv; + struct sk_buff *skb; + + if (do_debug) + ctcm_pr_debug("ctcmpc enter: %s(): cp=%i ch=0x%p id=%s\n", + __FUNCTION__, smp_processor_id(), ch, ch->id); + + fsm_deltimer(&ch->timer); + if (ch->retry++ > 3) { + struct mpc_group *gptr = priv->mpcg; + ctcm_pr_debug("%s: TX retry failed, restarting channel\n", + dev->name); + fsm_event(priv->fsm, DEV_EVENT_TXDOWN, dev); + /* call restart if not MPC or if MPC and mpcg fsm is ready. + use gptr as mpc indicator */ + if (!(gptr && (fsm_getstate(gptr->fsm) != MPCG_STATE_READY))) + ctcm_chx_restart(fi, event, arg); + goto done; + } + + ctcm_pr_debug("%s: TX retry %d\n", dev->name, ch->retry); + skb = skb_peek(&ch->io_queue); + if (skb) { + int rc = 0; + unsigned long saveflags = 0; + clear_normalized_cda(&ch->ccw[4]); + ch->ccw[4].count = skb->len; + if (set_normalized_cda(&ch->ccw[4], skb->data)) { + ctcm_pr_debug("%s: IDAL alloc failed, chan restart\n", + dev->name); + fsm_event(priv->fsm, DEV_EVENT_TXDOWN, dev); + ctcm_chx_restart(fi, event, arg); + goto done; + } + fsm_addtimer(&ch->timer, 1000, CTC_EVENT_TIMER, ch); + if (event == CTC_EVENT_TIMER) /* for TIMER not yet locked */ + spin_lock_irqsave(get_ccwdev_lock(ch->cdev), saveflags); + /* Such conditional locking is a known problem for + * sparse because its undeterministic in static view. + * Warnings should be ignored here. */ + if (do_debug_ccw) + ctcmpc_dumpit((char *)&ch->ccw[3], + sizeof(struct ccw1) * 3); + + rc = ccw_device_start(ch->cdev, &ch->ccw[3], + (unsigned long)ch, 0xff, 0); + if (event == CTC_EVENT_TIMER) + spin_unlock_irqrestore(get_ccwdev_lock(ch->cdev), + saveflags); + if (rc != 0) { + fsm_deltimer(&ch->timer); + ctcm_ccw_check_rc(ch, rc, "TX in chx_txretry"); + ctcm_purge_skb_queue(&ch->io_queue); + } + } +done: + return; +} + +/** + * 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 ctcm_chx_iofatal(fsm_instance *fi, int event, void *arg) +{ + struct channel *ch = (struct channel *)arg; + struct net_device *dev = ch->netdev; + + CTCM_DBF_TEXT(TRACE, 3, __FUNCTION__); + fsm_deltimer(&ch->timer); + ctcm_pr_warn("%s %s : unrecoverable channel error\n", + CTC_DRIVER_NAME, dev->name); + if (IS_MPC(ch)) { + ((struct ctcm_priv *)dev->priv)->stats.tx_dropped++; + ((struct ctcm_priv *)dev->priv)->stats.tx_errors++; + } + + if (CHANNEL_DIRECTION(ch->flags) == READ) { + ctcm_pr_debug("%s: RX I/O error\n", dev->name); + fsm_newstate(fi, CTC_STATE_RXERR); + fsm_event(((struct ctcm_priv *)dev->priv)->fsm, + DEV_EVENT_RXDOWN, dev); + } else { + ctcm_pr_debug("%s: TX I/O error\n", dev->name); + fsm_newstate(fi, CTC_STATE_TXERR); + fsm_event(((struct ctcm_priv *)dev->priv)->fsm, + DEV_EVENT_TXDOWN, dev); + } +} + +/* + * The ctcm statemachine for a channel. + */ +const fsm_node ch_fsm[] = { + { CTC_STATE_STOPPED, CTC_EVENT_STOP, fsm_action_nop }, + { CTC_STATE_STOPPED, CTC_EVENT_START, ctcm_chx_start }, + { CTC_STATE_STOPPED, CTC_EVENT_FINSTAT, fsm_action_nop }, + { CTC_STATE_STOPPED, CTC_EVENT_MC_FAIL, fsm_action_nop }, + + { CTC_STATE_NOTOP, CTC_EVENT_STOP, ctcm_chx_stop }, + { CTC_STATE_NOTOP, CTC_EVENT_START, fsm_action_nop }, + { CTC_STATE_NOTOP, CTC_EVENT_FINSTAT, fsm_action_nop }, + { CTC_STATE_NOTOP, CTC_EVENT_MC_FAIL, fsm_action_nop }, + { CTC_STATE_NOTOP, CTC_EVENT_MC_GOOD, ctcm_chx_start }, + + { CTC_STATE_STARTWAIT, CTC_EVENT_STOP, ctcm_chx_haltio }, + { CTC_STATE_STARTWAIT, CTC_EVENT_START, fsm_action_nop }, + { CTC_STATE_STARTWAIT, CTC_EVENT_FINSTAT, ctcm_chx_setmode }, + { CTC_STATE_STARTWAIT, CTC_EVENT_TIMER, ctcm_chx_setuperr }, + { CTC_STATE_STARTWAIT, CTC_EVENT_IO_ENODEV, ctcm_chx_iofatal }, + { CTC_STATE_STARTWAIT, CTC_EVENT_MC_FAIL, ctcm_chx_fail }, + + { CTC_STATE_STARTRETRY, CTC_EVENT_STOP, ctcm_chx_haltio }, + { CTC_STATE_STARTRETRY, CTC_EVENT_TIMER, ctcm_chx_setmode }, + { CTC_STATE_STARTRETRY, CTC_EVENT_FINSTAT, fsm_action_nop }, + { CTC_STATE_STARTRETRY, CTC_EVENT_MC_FAIL, ctcm_chx_fail }, + + { CTC_STATE_SETUPWAIT, CTC_EVENT_STOP, ctcm_chx_haltio }, + { CTC_STATE_SETUPWAIT, CTC_EVENT_START, fsm_action_nop }, + { CTC_STATE_SETUPWAIT, CTC_EVENT_FINSTAT, chx_firstio }, + { CTC_STATE_SETUPWAIT, CTC_EVENT_UC_RCRESET, ctcm_chx_setuperr }, + { CTC_STATE_SETUPWAIT, CTC_EVENT_UC_RSRESET, ctcm_chx_setuperr }, + { CTC_STATE_SETUPWAIT, CTC_EVENT_TIMER, ctcm_chx_setmode }, + { CTC_STATE_SETUPWAIT, CTC_EVENT_IO_ENODEV, ctcm_chx_iofatal }, + { CTC_STATE_SETUPWAIT, CTC_EVENT_MC_FAIL, ctcm_chx_fail }, + + { CTC_STATE_RXINIT, CTC_EVENT_STOP, ctcm_chx_haltio }, + { CTC_STATE_RXINIT, CTC_EVENT_START, fsm_action_nop }, + { CTC_STATE_RXINIT, CTC_EVENT_FINSTAT, chx_rxidle }, + { CTC_STATE_RXINIT, CTC_EVENT_UC_RCRESET, ctcm_chx_rxiniterr }, + { CTC_STATE_RXINIT, CTC_EVENT_UC_RSRESET, ctcm_chx_rxiniterr }, + { CTC_STATE_RXINIT, CTC_EVENT_TIMER, ctcm_chx_rxiniterr }, + { CTC_STATE_RXINIT, CTC_EVENT_ATTNBUSY, ctcm_chx_rxinitfail }, + { CTC_STATE_RXINIT, CTC_EVENT_IO_ENODEV, ctcm_chx_iofatal }, + { CTC_STATE_RXINIT, CTC_EVENT_UC_ZERO, chx_firstio }, + { CTC_STATE_RXINIT, CTC_EVENT_MC_FAIL, ctcm_chx_fail }, + + { CTC_STATE_RXIDLE, CTC_EVENT_STOP, ctcm_chx_haltio }, + { CTC_STATE_RXIDLE, CTC_EVENT_START, fsm_action_nop }, + { CTC_STATE_RXIDLE, CTC_EVENT_FINSTAT, chx_rx }, + { CTC_STATE_RXIDLE, CTC_EVENT_UC_RCRESET, ctcm_chx_rxdisc }, + { CTC_STATE_RXIDLE, CTC_EVENT_IO_ENODEV, ctcm_chx_iofatal }, + { CTC_STATE_RXIDLE, CTC_EVENT_MC_FAIL, ctcm_chx_fail }, + { CTC_STATE_RXIDLE, CTC_EVENT_UC_ZERO, chx_rx }, + + { CTC_STATE_TXINIT, CTC_EVENT_STOP, ctcm_chx_haltio }, + { CTC_STATE_TXINIT, CTC_EVENT_START, fsm_action_nop }, + { CTC_STATE_TXINIT, CTC_EVENT_FINSTAT, ctcm_chx_txidle }, + { CTC_STATE_TXINIT, CTC_EVENT_UC_RCRESET, ctcm_chx_txiniterr }, + { CTC_STATE_TXINIT, CTC_EVENT_UC_RSRESET, ctcm_chx_txiniterr }, + { CTC_STATE_TXINIT, CTC_EVENT_TIMER, ctcm_chx_txiniterr }, + { CTC_STATE_TXINIT, CTC_EVENT_IO_ENODEV, ctcm_chx_iofatal }, + { CTC_STATE_TXINIT, CTC_EVENT_MC_FAIL, ctcm_chx_fail }, + + { CTC_STATE_TXIDLE, CTC_EVENT_STOP, ctcm_chx_haltio }, + { CTC_STATE_TXIDLE, CTC_EVENT_START, fsm_action_nop }, + { CTC_STATE_TXIDLE, CTC_EVENT_FINSTAT, chx_firstio }, + { CTC_STATE_TXIDLE, CTC_EVENT_UC_RCRESET, fsm_action_nop }, + { CTC_STATE_TXIDLE, CTC_EVENT_UC_RSRESET, fsm_action_nop }, + { CTC_STATE_TXIDLE, CTC_EVENT_IO_ENODEV, ctcm_chx_iofatal }, + { CTC_STATE_TXIDLE, CTC_EVENT_MC_FAIL, ctcm_chx_fail }, + + { CTC_STATE_TERM, CTC_EVENT_STOP, fsm_action_nop }, + { CTC_STATE_TERM, CTC_EVENT_START, ctcm_chx_restart }, + { CTC_STATE_TERM, CTC_EVENT_FINSTAT, ctcm_chx_stopped }, + { CTC_STATE_TERM, CTC_EVENT_UC_RCRESET, fsm_action_nop }, + { CTC_STATE_TERM, CTC_EVENT_UC_RSRESET, fsm_action_nop }, + { CTC_STATE_TERM, CTC_EVENT_MC_FAIL, ctcm_chx_fail }, + + { CTC_STATE_DTERM, CTC_EVENT_STOP, ctcm_chx_haltio }, + { CTC_STATE_DTERM, CTC_EVENT_START, ctcm_chx_restart }, + { CTC_STATE_DTERM, CTC_EVENT_FINSTAT, ctcm_chx_setmode }, + { CTC_STATE_DTERM, CTC_EVENT_UC_RCRESET, fsm_action_nop }, + { CTC_STATE_DTERM, CTC_EVENT_UC_RSRESET, fsm_action_nop }, + { CTC_STATE_DTERM, CTC_EVENT_MC_FAIL, ctcm_chx_fail }, + + { CTC_STATE_TX, CTC_EVENT_STOP, ctcm_chx_haltio }, + { CTC_STATE_TX, CTC_EVENT_START, fsm_action_nop }, + { CTC_STATE_TX, CTC_EVENT_FINSTAT, chx_txdone }, + { CTC_STATE_TX, CTC_EVENT_UC_RCRESET, ctcm_chx_txretry }, + { CTC_STATE_TX, CTC_EVENT_UC_RSRESET, ctcm_chx_txretry }, + { CTC_STATE_TX, CTC_EVENT_TIMER, ctcm_chx_txretry }, + { CTC_STATE_TX, CTC_EVENT_IO_ENODEV, ctcm_chx_iofatal }, + { CTC_STATE_TX, CTC_EVENT_MC_FAIL, ctcm_chx_fail }, + + { CTC_STATE_RXERR, CTC_EVENT_STOP, ctcm_chx_haltio }, + { CTC_STATE_TXERR, CTC_EVENT_STOP, ctcm_chx_haltio }, + { CTC_STATE_TXERR, CTC_EVENT_MC_FAIL, ctcm_chx_fail }, + { CTC_STATE_RXERR, CTC_EVENT_MC_FAIL, ctcm_chx_fail }, +}; + +int ch_fsm_len = ARRAY_SIZE(ch_fsm); + +/* + * MPC actions for mpc channel statemachine + * handling of MPC protocol requires extra + * statemachine and actions which are prefixed ctcmpc_ . + * The ctc_ch_states and ctc_ch_state_names, + * ctc_ch_events and ctc_ch_event_names share the ctcm definitions + * which are expanded by some elements. + */ + +/* + * Actions for mpc channel statemachine. + */ + +/** + * 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 ctcmpc_chx_txdone(fsm_instance *fi, int event, void *arg) +{ + struct channel *ch = (struct channel *)arg; + struct net_device *dev = ch->netdev; + struct ctcm_priv *privptr = dev->priv; + struct mpc_group *grpptr = privptr->mpcg; + struct sk_buff *skb; + int first = 1; + int i; + struct timespec done_stamp; + __u32 data_space; + unsigned long duration; + struct sk_buff *peekskb; + int rc; + struct th_header *header; + struct pdu *p_header; + + if (do_debug) + ctcm_pr_debug("%s cp:%i enter: %s()\n", + dev->name, smp_processor_id(), __FUNCTION__); + + done_stamp = current_kernel_time(); /* xtime */ + duration = (done_stamp.tv_sec - ch->prof.send_stamp.tv_sec) * 1000000 + + (done_stamp.tv_nsec - ch->prof.send_stamp.tv_nsec) / 1000; + if (duration > ch->prof.tx_time) + ch->prof.tx_time = duration; + + if (ch->irb->scsw.count != 0) + ctcm_pr_debug("%s: TX not complete, remaining %d bytes\n", + dev->name, ch->irb->scsw.count); + fsm_deltimer(&ch->timer); + while ((skb = skb_dequeue(&ch->io_queue))) { + privptr->stats.tx_packets++; + privptr->stats.tx_bytes += skb->len - TH_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) || (grpptr->in_sweep != 0)) { + spin_unlock(&ch->collect_lock); + fsm_newstate(fi, CTC_STATE_TXIDLE); + goto done; + } + + if (ctcm_checkalloc_buffer(ch)) { + spin_unlock(&ch->collect_lock); + goto done; + } + ch->trans_skb->data = ch->trans_skb_data; + skb_reset_tail_pointer(ch->trans_skb); + ch->trans_skb->len = 0; + if (ch->prof.maxmulti < (ch->collect_len + TH_HEADER_LENGTH)) + ch->prof.maxmulti = ch->collect_len + TH_HEADER_LENGTH; + if (ch->prof.maxcqueue < skb_queue_len(&ch->collect_queue)) + ch->prof.maxcqueue = skb_queue_len(&ch->collect_queue); + i = 0; + + if (do_debug_data) + ctcm_pr_debug("ctcmpc: %s() building " + "trans_skb from collect_q \n", __FUNCTION__); + + data_space = grpptr->group_max_buflen - TH_HEADER_LENGTH; + + if (do_debug_data) + ctcm_pr_debug("ctcmpc: %s() building trans_skb from collect_q" + " data_space:%04x\n", __FUNCTION__, data_space); + p_header = NULL; + while ((skb = skb_dequeue(&ch->collect_queue))) { + memcpy(skb_put(ch->trans_skb, skb->len), skb->data, skb->len); + p_header = (struct pdu *) + (skb_tail_pointer(ch->trans_skb) - skb->len); + p_header->pdu_flag = 0x00; + if (skb->protocol == ntohs(ETH_P_SNAP)) + p_header->pdu_flag |= 0x60; + else + p_header->pdu_flag |= 0x20; + + if (do_debug_data) { + ctcm_pr_debug("ctcmpc: %s()trans_skb len:%04x \n", + __FUNCTION__, ch->trans_skb->len); + ctcm_pr_debug("ctcmpc: %s() pdu header and data" + " for up to 32 bytes sent to vtam\n", + __FUNCTION__); + ctcmpc_dumpit((char *)p_header, min((int)skb->len, 32)); + } + ch->collect_len -= skb->len; + data_space -= skb->len; + privptr->stats.tx_packets++; + privptr->stats.tx_bytes += skb->len; + atomic_dec(&skb->users); + dev_kfree_skb_any(skb); + peekskb = skb_peek(&ch->collect_queue); + if (peekskb->len > data_space) + break; + i++; + } + /* p_header points to the last one we handled */ + if (p_header) + p_header->pdu_flag |= PDU_LAST; /*Say it's the last one*/ + header = kzalloc(TH_HEADER_LENGTH, gfp_type()); + + if (!header) { + printk(KERN_WARNING "ctcmpc: OUT OF MEMORY IN %s()" + ": Data Lost \n", __FUNCTION__); + spin_unlock(&ch->collect_lock); + fsm_event(privptr->mpcg->fsm, MPCG_EVENT_INOP, dev); + goto done; + } + + header->th_ch_flag = TH_HAS_PDU; /* Normal data */ + ch->th_seq_num++; + header->th_seq_num = ch->th_seq_num; + + if (do_debug_data) + ctcm_pr_debug("%s: ToVTAM_th_seq= %08x\n" , + __FUNCTION__, ch->th_seq_num); + + memcpy(skb_push(ch->trans_skb, TH_HEADER_LENGTH), header, + TH_HEADER_LENGTH); /* put the TH on the packet */ + + kfree(header); + + if (do_debug_data) { + ctcm_pr_debug("ctcmpc: %s()trans_skb len:%04x \n", + __FUNCTION__, ch->trans_skb->len); + + ctcm_pr_debug("ctcmpc: %s() up-to-50 bytes of trans_skb " + "data to vtam from collect_q\n", __FUNCTION__); + ctcmpc_dumpit((char *)ch->trans_skb->data, + min((int)ch->trans_skb->len, 50)); + } + + spin_unlock(&ch->collect_lock); + clear_normalized_cda(&ch->ccw[1]); + if (set_normalized_cda(&ch->ccw[1], ch->trans_skb->data)) { + dev_kfree_skb_any(ch->trans_skb); + ch->trans_skb = NULL; + printk(KERN_WARNING + "ctcmpc: %s()CCW failure - data lost\n", + __FUNCTION__); + fsm_event(privptr->mpcg->fsm, MPCG_EVENT_INOP, dev); + return; + } + ch->ccw[1].count = ch->trans_skb->len; + fsm_addtimer(&ch->timer, CTCM_TIME_5_SEC, CTC_EVENT_TIMER, ch); + ch->prof.send_stamp = current_kernel_time(); /* xtime */ + if (do_debug_ccw) + ctcmpc_dumpit((char *)&ch->ccw[0], sizeof(struct ccw1) * 3); + rc = ccw_device_start(ch->cdev, &ch->ccw[0], + (unsigned long)ch, 0xff, 0); + ch->prof.doios_multi++; + if (rc != 0) { + privptr->stats.tx_dropped += i; + privptr->stats.tx_errors += i; + fsm_deltimer(&ch->timer); + ctcm_ccw_check_rc(ch, rc, "chained TX"); + } +done: + ctcm_clear_busy(dev); + ctcm_pr_debug("ctcmpc exit: %s %s()\n", dev->name, __FUNCTION__); + return; +} + +/** + * 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 ctcmpc_chx_rx(fsm_instance *fi, int event, void *arg) +{ + struct channel *ch = (struct channel *)arg; + struct net_device *dev = ch->netdev; + struct ctcm_priv *privptr = dev->priv; + struct mpc_group *grpptr = privptr->mpcg; + struct sk_buff *skb = ch->trans_skb; + struct sk_buff *new_skb; + unsigned long saveflags = 0; /* avoids compiler warning */ + int len = ch->max_bufsize - ch->irb->scsw.count; + + if (do_debug_data) { + CTCM_DBF_TEXT_(TRACE, CTC_DBF_DEBUG, "mpc_ch_rx %s cp:%i %s\n", + dev->name, smp_processor_id(), ch->id); + CTCM_DBF_TEXT_(TRACE, CTC_DBF_DEBUG, "mpc_ch_rx: maxbuf: %04x " + "len: %04x\n", ch->max_bufsize, len); + } + fsm_deltimer(&ch->timer); + + if (skb == NULL) { + ctcm_pr_debug("ctcmpc exit: %s() TRANS_SKB = NULL \n", + __FUNCTION__); + goto again; + } + + if (len < TH_HEADER_LENGTH) { + ctcm_pr_info("%s: got packet with invalid length %d\n", + dev->name, len); + privptr->stats.rx_dropped++; + privptr->stats.rx_length_errors++; + } else { + /* must have valid th header or game over */ + __u32 block_len = len; + len = TH_HEADER_LENGTH + XID2_LENGTH + 4; + new_skb = __dev_alloc_skb(ch->max_bufsize, GFP_ATOMIC); + + if (new_skb == NULL) { + printk(KERN_INFO "ctcmpc:%s() NEW_SKB = NULL\n", + __FUNCTION__); + printk(KERN_WARNING "ctcmpc: %s() MEMORY ALLOC FAILED" + " - DATA LOST - MPC FAILED\n", + __FUNCTION__); + fsm_event(privptr->mpcg->fsm, MPCG_EVENT_INOP, dev); + goto again; + } + switch (fsm_getstate(grpptr->fsm)) { + case MPCG_STATE_RESET: + case MPCG_STATE_INOP: + dev_kfree_skb_any(new_skb); + break; + case MPCG_STATE_FLOWC: + case MPCG_STATE_READY: + memcpy(skb_put(new_skb, block_len), + skb->data, block_len); + skb_queue_tail(&ch->io_queue, new_skb); + tasklet_schedule(&ch->ch_tasklet); + break; + default: + memcpy(skb_put(new_skb, len), skb->data, len); + skb_queue_tail(&ch->io_queue, new_skb); + tasklet_hi_schedule(&ch->ch_tasklet); + break; + } + } + +again: + switch (fsm_getstate(grpptr->fsm)) { + int rc, dolock; + case MPCG_STATE_FLOWC: + case MPCG_STATE_READY: + if (ctcm_checkalloc_buffer(ch)) + break; + ch->trans_skb->data = ch->trans_skb_data; + skb_reset_tail_pointer(ch->trans_skb); + ch->trans_skb->len = 0; + ch->ccw[1].count = ch->max_bufsize; + if (do_debug_ccw) + ctcmpc_dumpit((char *)&ch->ccw[0], + sizeof(struct ccw1) * 3); + dolock = !in_irq(); + if (dolock) + spin_lock_irqsave( + get_ccwdev_lock(ch->cdev), saveflags); + rc = ccw_device_start(ch->cdev, &ch->ccw[0], + (unsigned long)ch, 0xff, 0); + if (dolock) /* see remark about conditional locking */ + spin_unlock_irqrestore( + get_ccwdev_lock(ch->cdev), saveflags); + if (rc != 0) + ctcm_ccw_check_rc(ch, rc, "normal RX"); + default: + break; + } + + if (do_debug) + ctcm_pr_debug("ctcmpc exit : %s %s(): ch=0x%p id=%s\n", + dev->name, __FUNCTION__, ch, ch->id); + +} + +/** + * 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 ctcmpc_chx_firstio(fsm_instance *fi, int event, void *arg) +{ + struct channel *ch = (struct channel *)arg; + struct net_device *dev = ch->netdev; + + if (do_debug) { + struct mpc_group *gptr = ((struct ctcm_priv *)dev->priv)->mpcg; + ctcm_pr_debug("ctcmpc enter: %s(): ch=0x%p id=%s\n", + __FUNCTION__, ch, ch->id); + ctcm_pr_debug("%s() %s chstate:%i grpstate:%i chprotocol:%i\n", + __FUNCTION__, ch->id, fsm_getstate(fi), + fsm_getstate(gptr->fsm), ch->protocol); + } + if (fsm_getstate(fi) == CTC_STATE_TXIDLE) + MPC_DBF_DEV_NAME(TRACE, dev, "remote side issued READ? "); + + fsm_deltimer(&ch->timer); + if (ctcm_checkalloc_buffer(ch)) + goto done; + + switch (fsm_getstate(fi)) { + case CTC_STATE_STARTRETRY: + case CTC_STATE_SETUPWAIT: + if (CHANNEL_DIRECTION(ch->flags) == READ) { + ctcmpc_chx_rxidle(fi, event, arg); + } else { + fsm_newstate(fi, CTC_STATE_TXIDLE); + fsm_event(((struct ctcm_priv *)dev->priv)->fsm, + DEV_EVENT_TXUP, dev); + } + goto done; + default: + break; + }; + + fsm_newstate(fi, (CHANNEL_DIRECTION(ch->flags) == READ) + ? CTC_STATE_RXINIT : CTC_STATE_TXINIT); + +done: + if (do_debug) + ctcm_pr_debug("ctcmpc exit : %s(): ch=0x%p id=%s\n", + __FUNCTION__, ch, ch->id); + return; +} + +/** + * 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. + */ +void ctcmpc_chx_rxidle(fsm_instance *fi, int event, void *arg) +{ + struct channel *ch = (struct channel *)arg; + struct net_device *dev = ch->netdev; + struct ctcm_priv *privptr = (struct ctcm_priv *)dev->priv; + struct mpc_group *grpptr = privptr->mpcg; + int rc; + unsigned long saveflags = 0; /* avoids compiler warning */ + + fsm_deltimer(&ch->timer); + ctcm_pr_debug("%s cp:%i enter: %s()\n", + dev->name, smp_processor_id(), __FUNCTION__); + if (do_debug) + ctcm_pr_debug("%s() %s chstate:%i grpstate:%i\n", + __FUNCTION__, ch->id, + fsm_getstate(fi), fsm_getstate(grpptr->fsm)); + + fsm_newstate(fi, CTC_STATE_RXIDLE); + /* XID processing complete */ + + switch (fsm_getstate(grpptr->fsm)) { + case MPCG_STATE_FLOWC: + case MPCG_STATE_READY: + if (ctcm_checkalloc_buffer(ch)) + goto done; + ch->trans_skb->data = ch->trans_skb_data; + skb_reset_tail_pointer(ch->trans_skb); + ch->trans_skb->len = 0; + ch->ccw[1].count = ch->max_bufsize; + if (do_debug_ccw) + ctcmpc_dumpit((char *)&ch->ccw[0], + sizeof(struct ccw1) * 3); + if (event == CTC_EVENT_START) + /* see remark about conditional locking */ + spin_lock_irqsave(get_ccwdev_lock(ch->cdev), saveflags); + rc = ccw_device_start(ch->cdev, &ch->ccw[0], + (unsigned long)ch, 0xff, 0); + if (event == CTC_EVENT_START) + spin_unlock_irqrestore( + get_ccwdev_lock(ch->cdev), saveflags); + if (rc != 0) { + fsm_newstate(fi, CTC_STATE_RXINIT); + ctcm_ccw_check_rc(ch, rc, "initial RX"); + goto done; + } + break; + default: + break; + } + + fsm_event(((struct ctcm_priv *)dev->priv)->fsm, DEV_EVENT_RXUP, dev); +done: + if (do_debug) + ctcm_pr_debug("ctcmpc exit: %s %s()\n", + dev->name, __FUNCTION__); + return; +} + +/* + * ctcmpc channel FSM action + * called from several points in ctcmpc_ch_fsm + * ctcmpc only + */ +static void ctcmpc_chx_attn(fsm_instance *fsm, int event, void *arg) +{ + struct channel *ch = (struct channel *)arg; + struct net_device *dev = ch->netdev; + struct ctcm_priv *privptr = (struct ctcm_priv *)dev->priv; + struct mpc_group *grpptr = privptr->mpcg; + + if (do_debug) { + ctcm_pr_debug("ctcmpc enter: %s(): cp=%i ch=0x%p id=%s" + "GrpState:%s ChState:%s\n", + __FUNCTION__, smp_processor_id(), ch, ch->id, + fsm_getstate_str(grpptr->fsm), + fsm_getstate_str(ch->fsm)); + } + + switch (fsm_getstate(grpptr->fsm)) { + case MPCG_STATE_XID2INITW: + /* ok..start yside xid exchanges */ + if (!ch->in_mpcgroup) + break; + if (fsm_getstate(ch->fsm) == CH_XID0_PENDING) { + fsm_deltimer(&grpptr->timer); + fsm_addtimer(&grpptr->timer, + MPC_XID_TIMEOUT_VALUE, + MPCG_EVENT_TIMER, dev); + fsm_event(grpptr->fsm, MPCG_EVENT_XID0DO, ch); + + } else if (fsm_getstate(ch->fsm) < CH_XID7_PENDING1) + /* attn rcvd before xid0 processed via bh */ + fsm_newstate(ch->fsm, CH_XID7_PENDING1); + break; + case MPCG_STATE_XID2INITX: + case MPCG_STATE_XID0IOWAIT: + case MPCG_STATE_XID0IOWAIX: + /* attn rcvd before xid0 processed on ch + but mid-xid0 processing for group */ + if (fsm_getstate(ch->fsm) < CH_XID7_PENDING1) + fsm_newstate(ch->fsm, CH_XID7_PENDING1); + break; + case MPCG_STATE_XID7INITW: + case MPCG_STATE_XID7INITX: + case MPCG_STATE_XID7INITI: + case MPCG_STATE_XID7INITZ: + switch (fsm_getstate(ch->fsm)) { + case CH_XID7_PENDING: + fsm_newstate(ch->fsm, CH_XID7_PENDING1); + break; + case CH_XID7_PENDING2: + fsm_newstate(ch->fsm, CH_XID7_PENDING3); + break; + } + fsm_event(grpptr->fsm, MPCG_EVENT_XID7DONE, dev); + break; + } + + if (do_debug) + ctcm_pr_debug("ctcmpc exit : %s(): cp=%i ch=0x%p id=%s\n", + __FUNCTION__, smp_processor_id(), ch, ch->id); + return; + +} + +/* + * ctcmpc channel FSM action + * called from one point in ctcmpc_ch_fsm + * ctcmpc only + */ +static void ctcmpc_chx_attnbusy(fsm_instance *fsm, int event, void *arg) +{ + struct channel *ch = (struct channel *)arg; + struct net_device *dev = ch->netdev; + struct ctcm_priv *privptr = (struct ctcm_priv *)dev->priv; + struct mpc_group *grpptr = privptr->mpcg; + + ctcm_pr_debug("ctcmpc enter: %s %s() %s \nGrpState:%s ChState:%s\n", + dev->name, + __FUNCTION__, ch->id, + fsm_getstate_str(grpptr->fsm), + fsm_getstate_str(ch->fsm)); + + fsm_deltimer(&ch->timer); + + switch (fsm_getstate(grpptr->fsm)) { + case MPCG_STATE_XID0IOWAIT: + /* vtam wants to be primary.start yside xid exchanges*/ + /* only receive one attn-busy at a time so must not */ + /* change state each time */ + grpptr->changed_side = 1; + fsm_newstate(grpptr->fsm, MPCG_STATE_XID2INITW); + break; + case MPCG_STATE_XID2INITW: + if (grpptr->changed_side == 1) { + grpptr->changed_side = 2; + break; + } + /* process began via call to establish_conn */ + /* so must report failure instead of reverting */ + /* back to ready-for-xid passive state */ + if (grpptr->estconnfunc) + goto done; + /* this attnbusy is NOT the result of xside xid */ + /* collisions so yside must have been triggered */ + /* by an ATTN that was not intended to start XID */ + /* processing. Revert back to ready-for-xid and */ + /* wait for ATTN interrupt to signal xid start */ + if (fsm_getstate(ch->fsm) == CH_XID0_INPROGRESS) { + fsm_newstate(ch->fsm, CH_XID0_PENDING) ; + fsm_deltimer(&grpptr->timer); + goto done; + } + fsm_event(grpptr->fsm, MPCG_EVENT_INOP, dev); + goto done; + case MPCG_STATE_XID2INITX: + /* XID2 was received before ATTN Busy for second + channel.Send yside xid for second channel. + */ + if (grpptr->changed_side == 1) { + grpptr->changed_side = 2; + break; + } + case MPCG_STATE_XID0IOWAIX: + case MPCG_STATE_XID7INITW: + case MPCG_STATE_XID7INITX: + case MPCG_STATE_XID7INITI: + case MPCG_STATE_XID7INITZ: + default: + /* multiple attn-busy indicates too out-of-sync */ + /* and they are certainly not being received as part */ + /* of valid mpc group negotiations.. */ + fsm_event(grpptr->fsm, MPCG_EVENT_INOP, dev); + goto done; + } + + if (grpptr->changed_side == 1) { + fsm_deltimer(&grpptr->timer); + fsm_addtimer(&grpptr->timer, MPC_XID_TIMEOUT_VALUE, + MPCG_EVENT_TIMER, dev); + } + if (ch->in_mpcgroup) + fsm_event(grpptr->fsm, MPCG_EVENT_XID0DO, ch); + else + printk(KERN_WARNING "ctcmpc: %s() Not all channels have" + " been added to group\n", __FUNCTION__); + +done: + if (do_debug) + ctcm_pr_debug("ctcmpc exit : %s()%s ch=0x%p id=%s\n", + __FUNCTION__, dev->name, ch, ch->id); + + return; + +} + +/* + * ctcmpc channel FSM action + * called from several points in ctcmpc_ch_fsm + * ctcmpc only + */ +static void ctcmpc_chx_resend(fsm_instance *fsm, int event, void *arg) +{ + struct channel *ch = (struct channel *)arg; + struct net_device *dev = ch->netdev; + struct ctcm_priv *privptr = (struct ctcm_priv *)dev->priv; + struct mpc_group *grpptr = privptr->mpcg; + + ctcm_pr_debug("ctcmpc enter: %s %s() %s \nGrpState:%s ChState:%s\n", + dev->name, __FUNCTION__, ch->id, + fsm_getstate_str(grpptr->fsm), + fsm_getstate_str(ch->fsm)); + + fsm_event(grpptr->fsm, MPCG_EVENT_XID0DO, ch); + + return; +} + +/* + * ctcmpc channel FSM action + * called from several points in ctcmpc_ch_fsm + * ctcmpc only + */ +static void ctcmpc_chx_send_sweep(fsm_instance *fsm, int event, void *arg) +{ + struct channel *ach = (struct channel *)arg; + struct net_device *dev = ach->netdev; + struct ctcm_priv *privptr = (struct ctcm_priv *)dev->priv; + struct mpc_group *grpptr = privptr->mpcg; + struct channel *wch = privptr->channel[WRITE]; + struct channel *rch = privptr->channel[READ]; + struct sk_buff *skb; + struct th_sweep *header; + int rc = 0; + unsigned long saveflags = 0; + + if (do_debug) + ctcm_pr_debug("ctcmpc enter: %s(): cp=%i ch=0x%p id=%s\n", + __FUNCTION__, smp_processor_id(), ach, ach->id); + + if (grpptr->in_sweep == 0) + goto done; + + if (do_debug_data) { + ctcm_pr_debug("ctcmpc: %s() 1: ToVTAM_th_seq= %08x\n" , + __FUNCTION__, wch->th_seq_num); + ctcm_pr_debug("ctcmpc: %s() 1: FromVTAM_th_seq= %08x\n" , + __FUNCTION__, rch->th_seq_num); + } + + if (fsm_getstate(wch->fsm) != CTC_STATE_TXIDLE) { + /* give the previous IO time to complete */ + fsm_addtimer(&wch->sweep_timer, + 200, CTC_EVENT_RSWEEP_TIMER, wch); + goto done; + } + + skb = skb_dequeue(&wch->sweep_queue); + if (!skb) + goto done; + + if (set_normalized_cda(&wch->ccw[4], skb->data)) { + grpptr->in_sweep = 0; + ctcm_clear_busy_do(dev); + dev_kfree_skb_any(skb); + fsm_event(grpptr->fsm, MPCG_EVENT_INOP, dev); + goto done; + } else { + atomic_inc(&skb->users); + skb_queue_tail(&wch->io_queue, skb); + } + + /* send out the sweep */ + wch->ccw[4].count = skb->len; + + header = (struct th_sweep *)skb->data; + switch (header->th.th_ch_flag) { + case TH_SWEEP_REQ: + grpptr->sweep_req_pend_num--; + break; + case TH_SWEEP_RESP: + grpptr->sweep_rsp_pend_num--; + break; + } + + header->sw.th_last_seq = wch->th_seq_num; + + if (do_debug_ccw) + ctcmpc_dumpit((char *)&wch->ccw[3], sizeof(struct ccw1) * 3); + + ctcm_pr_debug("ctcmpc: %s() sweep packet\n", __FUNCTION__); + ctcmpc_dumpit((char *)header, TH_SWEEP_LENGTH); + + fsm_addtimer(&wch->timer, CTCM_TIME_5_SEC, CTC_EVENT_TIMER, wch); + fsm_newstate(wch->fsm, CTC_STATE_TX); + + spin_lock_irqsave(get_ccwdev_lock(wch->cdev), saveflags); + wch->prof.send_stamp = current_kernel_time(); /* xtime */ + rc = ccw_device_start(wch->cdev, &wch->ccw[3], + (unsigned long) wch, 0xff, 0); + spin_unlock_irqrestore(get_ccwdev_lock(wch->cdev), saveflags); + + if ((grpptr->sweep_req_pend_num == 0) && + (grpptr->sweep_rsp_pend_num == 0)) { + grpptr->in_sweep = 0; + rch->th_seq_num = 0x00; + wch->th_seq_num = 0x00; + ctcm_clear_busy_do(dev); + } + + if (do_debug_data) { + ctcm_pr_debug("ctcmpc: %s()2: ToVTAM_th_seq= %08x\n" , + __FUNCTION__, wch->th_seq_num); + ctcm_pr_debug("ctcmpc: %s()2: FromVTAM_th_seq= %08x\n" , + __FUNCTION__, rch->th_seq_num); + } + + if (rc != 0) + ctcm_ccw_check_rc(wch, rc, "send sweep"); + +done: + if (do_debug) + ctcm_pr_debug("ctcmpc exit: %s() %s\n", __FUNCTION__, ach->id); + return; +} + + +/* + * The ctcmpc statemachine for a channel. + */ + +const fsm_node ctcmpc_ch_fsm[] = { + { CTC_STATE_STOPPED, CTC_EVENT_STOP, fsm_action_nop }, + { CTC_STATE_STOPPED, CTC_EVENT_START, ctcm_chx_start }, + { CTC_STATE_STOPPED, CTC_EVENT_IO_ENODEV, ctcm_chx_iofatal }, + { CTC_STATE_STOPPED, CTC_EVENT_FINSTAT, fsm_action_nop }, + { CTC_STATE_STOPPED, CTC_EVENT_MC_FAIL, fsm_action_nop }, + + { CTC_STATE_NOTOP, CTC_EVENT_STOP, ctcm_chx_stop }, + { CTC_STATE_NOTOP, CTC_EVENT_START, fsm_action_nop }, + { CTC_STATE_NOTOP, CTC_EVENT_FINSTAT, fsm_action_nop }, + { CTC_STATE_NOTOP, CTC_EVENT_MC_FAIL, fsm_action_nop }, + { CTC_STATE_NOTOP, CTC_EVENT_MC_GOOD, ctcm_chx_start }, + { CTC_STATE_NOTOP, CTC_EVENT_UC_RCRESET, ctcm_chx_stop }, + { CTC_STATE_NOTOP, CTC_EVENT_UC_RSRESET, ctcm_chx_stop }, + { CTC_STATE_NOTOP, CTC_EVENT_IO_ENODEV, ctcm_chx_iofatal }, + + { CTC_STATE_STARTWAIT, CTC_EVENT_STOP, ctcm_chx_haltio }, + { CTC_STATE_STARTWAIT, CTC_EVENT_START, fsm_action_nop }, + { CTC_STATE_STARTWAIT, CTC_EVENT_FINSTAT, ctcm_chx_setmode }, + { CTC_STATE_STARTWAIT, CTC_EVENT_TIMER, ctcm_chx_setuperr }, + { CTC_STATE_STARTWAIT, CTC_EVENT_IO_ENODEV, ctcm_chx_iofatal }, + { CTC_STATE_STARTWAIT, CTC_EVENT_MC_FAIL, ctcm_chx_fail }, + + { CTC_STATE_STARTRETRY, CTC_EVENT_STOP, ctcm_chx_haltio }, + { CTC_STATE_STARTRETRY, CTC_EVENT_TIMER, ctcm_chx_setmode }, + { CTC_STATE_STARTRETRY, CTC_EVENT_FINSTAT, ctcm_chx_setmode }, + { CTC_STATE_STARTRETRY, CTC_EVENT_MC_FAIL, ctcm_chx_fail }, + { CTC_STATE_STARTRETRY, CTC_EVENT_IO_ENODEV, ctcm_chx_iofatal }, + + { CTC_STATE_SETUPWAIT, CTC_EVENT_STOP, ctcm_chx_haltio }, + { CTC_STATE_SETUPWAIT, CTC_EVENT_START, fsm_action_nop }, + { CTC_STATE_SETUPWAIT, CTC_EVENT_FINSTAT, ctcmpc_chx_firstio }, + { CTC_STATE_SETUPWAIT, CTC_EVENT_UC_RCRESET, ctcm_chx_setuperr }, + { CTC_STATE_SETUPWAIT, CTC_EVENT_UC_RSRESET, ctcm_chx_setuperr }, + { CTC_STATE_SETUPWAIT, CTC_EVENT_TIMER, ctcm_chx_setmode }, + { CTC_STATE_SETUPWAIT, CTC_EVENT_IO_ENODEV, ctcm_chx_iofatal }, + { CTC_STATE_SETUPWAIT, CTC_EVENT_MC_FAIL, ctcm_chx_fail }, + + { CTC_STATE_RXINIT, CTC_EVENT_STOP, ctcm_chx_haltio }, + { CTC_STATE_RXINIT, CTC_EVENT_START, fsm_action_nop }, + { CTC_STATE_RXINIT, CTC_EVENT_FINSTAT, ctcmpc_chx_rxidle }, + { CTC_STATE_RXINIT, CTC_EVENT_UC_RCRESET, ctcm_chx_rxiniterr }, + { CTC_STATE_RXINIT, CTC_EVENT_UC_RSRESET, ctcm_chx_rxiniterr }, + { CTC_STATE_RXINIT, CTC_EVENT_TIMER, ctcm_chx_rxiniterr }, + { CTC_STATE_RXINIT, CTC_EVENT_ATTNBUSY, ctcm_chx_rxinitfail }, + { CTC_STATE_RXINIT, CTC_EVENT_IO_ENODEV, ctcm_chx_iofatal }, + { CTC_STATE_RXINIT, CTC_EVENT_UC_ZERO, ctcmpc_chx_firstio }, + { CTC_STATE_RXINIT, CTC_EVENT_MC_FAIL, ctcm_chx_fail }, + + { CH_XID0_PENDING, CTC_EVENT_FINSTAT, fsm_action_nop }, + { CH_XID0_PENDING, CTC_EVENT_ATTN, ctcmpc_chx_attn }, + { CH_XID0_PENDING, CTC_EVENT_STOP, ctcm_chx_haltio }, + { CH_XID0_PENDING, CTC_EVENT_START, fsm_action_nop }, + { CH_XID0_PENDING, CTC_EVENT_IO_ENODEV, ctcm_chx_iofatal }, + { CH_XID0_PENDING, CTC_EVENT_MC_FAIL, ctcm_chx_fail }, + { CH_XID0_PENDING, CTC_EVENT_UC_RCRESET, ctcm_chx_setuperr }, + { CH_XID0_PENDING, CTC_EVENT_UC_RSRESET, ctcm_chx_setuperr }, + { CH_XID0_PENDING, CTC_EVENT_UC_RSRESET, ctcm_chx_setuperr }, + { CH_XID0_PENDING, CTC_EVENT_ATTNBUSY, ctcm_chx_iofatal }, + + { CH_XID0_INPROGRESS, CTC_EVENT_FINSTAT, ctcmpc_chx_rx }, + { CH_XID0_INPROGRESS, CTC_EVENT_ATTN, ctcmpc_chx_attn }, + { CH_XID0_INPROGRESS, CTC_EVENT_STOP, ctcm_chx_haltio }, + { CH_XID0_INPROGRESS, CTC_EVENT_START, fsm_action_nop }, + { CH_XID0_INPROGRESS, CTC_EVENT_IO_ENODEV, ctcm_chx_iofatal }, + { CH_XID0_INPROGRESS, CTC_EVENT_MC_FAIL, ctcm_chx_fail }, + { CH_XID0_INPROGRESS, CTC_EVENT_UC_ZERO, ctcmpc_chx_rx }, + { CH_XID0_INPROGRESS, CTC_EVENT_UC_RCRESET, ctcm_chx_setuperr }, + { CH_XID0_INPROGRESS, CTC_EVENT_ATTNBUSY, ctcmpc_chx_attnbusy }, + { CH_XID0_INPROGRESS, CTC_EVENT_TIMER, ctcmpc_chx_resend }, + { CH_XID0_INPROGRESS, CTC_EVENT_IO_EBUSY, ctcm_chx_fail }, + + { CH_XID7_PENDING, CTC_EVENT_FINSTAT, ctcmpc_chx_rx }, + { CH_XID7_PENDING, CTC_EVENT_ATTN, ctcmpc_chx_attn }, + { CH_XID7_PENDING, CTC_EVENT_STOP, ctcm_chx_haltio }, + { CH_XID7_PENDING, CTC_EVENT_START, fsm_action_nop }, + { CH_XID7_PENDING, CTC_EVENT_IO_ENODEV, ctcm_chx_iofatal }, + { CH_XID7_PENDING, CTC_EVENT_MC_FAIL, ctcm_chx_fail }, + { CH_XID7_PENDING, CTC_EVENT_UC_ZERO, ctcmpc_chx_rx }, + { CH_XID7_PENDING, CTC_EVENT_UC_RCRESET, ctcm_chx_setuperr }, + { CH_XID7_PENDING, CTC_EVENT_UC_RSRESET, ctcm_chx_setuperr }, + { CH_XID7_PENDING, CTC_EVENT_UC_RSRESET, ctcm_chx_setuperr }, + { CH_XID7_PENDING, CTC_EVENT_ATTNBUSY, ctcm_chx_iofatal }, + { CH_XID7_PENDING, CTC_EVENT_TIMER, ctcmpc_chx_resend }, + { CH_XID7_PENDING, CTC_EVENT_IO_EBUSY, ctcm_chx_fail }, + + { CH_XID7_PENDING1, CTC_EVENT_FINSTAT, ctcmpc_chx_rx }, + { CH_XID7_PENDING1, CTC_EVENT_ATTN, ctcmpc_chx_attn }, + { CH_XID7_PENDING1, CTC_EVENT_STOP, ctcm_chx_haltio }, + { CH_XID7_PENDING1, CTC_EVENT_START, fsm_action_nop }, + { CH_XID7_PENDING1, CTC_EVENT_IO_ENODEV, ctcm_chx_iofatal }, + { CH_XID7_PENDING1, CTC_EVENT_MC_FAIL, ctcm_chx_fail }, + { CH_XID7_PENDING1, CTC_EVENT_UC_ZERO, ctcmpc_chx_rx }, + { CH_XID7_PENDING1, CTC_EVENT_UC_RCRESET, ctcm_chx_setuperr }, + { CH_XID7_PENDING1, CTC_EVENT_UC_RSRESET, ctcm_chx_setuperr }, + { CH_XID7_PENDING1, CTC_EVENT_ATTNBUSY, ctcm_chx_iofatal }, + { CH_XID7_PENDING1, CTC_EVENT_TIMER, ctcmpc_chx_resend }, + { CH_XID7_PENDING1, CTC_EVENT_IO_EBUSY, ctcm_chx_fail }, + + { CH_XID7_PENDING2, CTC_EVENT_FINSTAT, ctcmpc_chx_rx }, + { CH_XID7_PENDING2, CTC_EVENT_ATTN, ctcmpc_chx_attn }, + { CH_XID7_PENDING2, CTC_EVENT_STOP, ctcm_chx_haltio }, + { CH_XID7_PENDING2, CTC_EVENT_START, fsm_action_nop }, + { CH_XID7_PENDING2, CTC_EVENT_IO_ENODEV, ctcm_chx_iofatal }, + { CH_XID7_PENDING2, CTC_EVENT_MC_FAIL, ctcm_chx_fail }, + { CH_XID7_PENDING2, CTC_EVENT_UC_ZERO, ctcmpc_chx_rx }, + { CH_XID7_PENDING2, CTC_EVENT_UC_RCRESET, ctcm_chx_setuperr }, + { CH_XID7_PENDING2, CTC_EVENT_UC_RSRESET, ctcm_chx_setuperr }, + { CH_XID7_PENDING2, CTC_EVENT_ATTNBUSY, ctcm_chx_iofatal }, + { CH_XID7_PENDING2, CTC_EVENT_TIMER, ctcmpc_chx_resend }, + { CH_XID7_PENDING2, CTC_EVENT_IO_EBUSY, ctcm_chx_fail }, + + { CH_XID7_PENDING3, CTC_EVENT_FINSTAT, ctcmpc_chx_rx }, + { CH_XID7_PENDING3, CTC_EVENT_ATTN, ctcmpc_chx_attn }, + { CH_XID7_PENDING3, CTC_EVENT_STOP, ctcm_chx_haltio }, + { CH_XID7_PENDING3, CTC_EVENT_START, fsm_action_nop }, + { CH_XID7_PENDING3, CTC_EVENT_IO_ENODEV, ctcm_chx_iofatal }, + { CH_XID7_PENDING3, CTC_EVENT_MC_FAIL, ctcm_chx_fail }, + { CH_XID7_PENDING3, CTC_EVENT_UC_ZERO, ctcmpc_chx_rx }, + { CH_XID7_PENDING3, CTC_EVENT_UC_RCRESET, ctcm_chx_setuperr }, + { CH_XID7_PENDING3, CTC_EVENT_UC_RSRESET, ctcm_chx_setuperr }, + { CH_XID7_PENDING3, CTC_EVENT_ATTNBUSY, ctcm_chx_iofatal }, + { CH_XID7_PENDING3, CTC_EVENT_TIMER, ctcmpc_chx_resend }, + { CH_XID7_PENDING3, CTC_EVENT_IO_EBUSY, ctcm_chx_fail }, + + { CH_XID7_PENDING4, CTC_EVENT_FINSTAT, ctcmpc_chx_rx }, + { CH_XID7_PENDING4, CTC_EVENT_ATTN, ctcmpc_chx_attn }, + { CH_XID7_PENDING4, CTC_EVENT_STOP, ctcm_chx_haltio }, + { CH_XID7_PENDING4, CTC_EVENT_START, fsm_action_nop }, + { CH_XID7_PENDING4, CTC_EVENT_IO_ENODEV, ctcm_chx_iofatal }, + { CH_XID7_PENDING4, CTC_EVENT_MC_FAIL, ctcm_chx_fail }, + { CH_XID7_PENDING4, CTC_EVENT_UC_ZERO, ctcmpc_chx_rx }, + { CH_XID7_PENDING4, CTC_EVENT_UC_RCRESET, ctcm_chx_setuperr }, + { CH_XID7_PENDING4, CTC_EVENT_UC_RSRESET, ctcm_chx_setuperr }, + { CH_XID7_PENDING4, CTC_EVENT_ATTNBUSY, ctcm_chx_iofatal }, + { CH_XID7_PENDING4, CTC_EVENT_TIMER, ctcmpc_chx_resend }, + { CH_XID7_PENDING4, CTC_EVENT_IO_EBUSY, ctcm_chx_fail }, + + { CTC_STATE_RXIDLE, CTC_EVENT_STOP, ctcm_chx_haltio }, + { CTC_STATE_RXIDLE, CTC_EVENT_START, fsm_action_nop }, + { CTC_STATE_RXIDLE, CTC_EVENT_FINSTAT, ctcmpc_chx_rx }, + { CTC_STATE_RXIDLE, CTC_EVENT_UC_RCRESET, ctcm_chx_rxdisc }, + { CTC_STATE_RXIDLE, CTC_EVENT_UC_RSRESET, ctcm_chx_fail }, + { CTC_STATE_RXIDLE, CTC_EVENT_IO_ENODEV, ctcm_chx_iofatal }, + { CTC_STATE_RXIDLE, CTC_EVENT_MC_FAIL, ctcm_chx_fail }, + { CTC_STATE_RXIDLE, CTC_EVENT_UC_ZERO, ctcmpc_chx_rx }, + + { CTC_STATE_TXINIT, CTC_EVENT_STOP, ctcm_chx_haltio }, + { CTC_STATE_TXINIT, CTC_EVENT_START, fsm_action_nop }, + { CTC_STATE_TXINIT, CTC_EVENT_FINSTAT, ctcm_chx_txidle }, + { CTC_STATE_TXINIT, CTC_EVENT_UC_RCRESET, ctcm_chx_txiniterr }, + { CTC_STATE_TXINIT, CTC_EVENT_UC_RSRESET, ctcm_chx_txiniterr }, + { CTC_STATE_TXINIT, CTC_EVENT_TIMER, ctcm_chx_txiniterr }, + { CTC_STATE_TXINIT, CTC_EVENT_IO_ENODEV, ctcm_chx_iofatal }, + { CTC_STATE_TXINIT, CTC_EVENT_MC_FAIL, ctcm_chx_fail }, + { CTC_STATE_TXINIT, CTC_EVENT_RSWEEP_TIMER, ctcmpc_chx_send_sweep }, + + { CTC_STATE_TXIDLE, CTC_EVENT_STOP, ctcm_chx_haltio }, + { CTC_STATE_TXIDLE, CTC_EVENT_START, fsm_action_nop }, + { CTC_STATE_TXIDLE, CTC_EVENT_FINSTAT, ctcmpc_chx_firstio }, + { CTC_STATE_TXIDLE, CTC_EVENT_UC_RCRESET, ctcm_chx_fail }, + { CTC_STATE_TXIDLE, CTC_EVENT_UC_RSRESET, ctcm_chx_fail }, + { CTC_STATE_TXIDLE, CTC_EVENT_IO_ENODEV, ctcm_chx_iofatal }, + { CTC_STATE_TXIDLE, CTC_EVENT_MC_FAIL, ctcm_chx_fail }, + { CTC_STATE_TXIDLE, CTC_EVENT_RSWEEP_TIMER, ctcmpc_chx_send_sweep }, + + { CTC_STATE_TERM, CTC_EVENT_STOP, fsm_action_nop }, + { CTC_STATE_TERM, CTC_EVENT_START, ctcm_chx_restart }, + { CTC_STATE_TERM, CTC_EVENT_FINSTAT, ctcm_chx_stopped }, + { CTC_STATE_TERM, CTC_EVENT_UC_RCRESET, fsm_action_nop }, + { CTC_STATE_TERM, CTC_EVENT_UC_RSRESET, fsm_action_nop }, + { CTC_STATE_TERM, CTC_EVENT_MC_FAIL, ctcm_chx_fail }, + { CTC_STATE_TERM, CTC_EVENT_IO_EBUSY, ctcm_chx_fail }, + { CTC_STATE_TERM, CTC_EVENT_IO_ENODEV, ctcm_chx_iofatal }, + + { CTC_STATE_DTERM, CTC_EVENT_STOP, ctcm_chx_haltio }, + { CTC_STATE_DTERM, CTC_EVENT_START, ctcm_chx_restart }, + { CTC_STATE_DTERM, CTC_EVENT_FINSTAT, ctcm_chx_setmode }, + { CTC_STATE_DTERM, CTC_EVENT_UC_RCRESET, fsm_action_nop }, + { CTC_STATE_DTERM, CTC_EVENT_UC_RSRESET, fsm_action_nop }, + { CTC_STATE_DTERM, CTC_EVENT_MC_FAIL, ctcm_chx_fail }, + { CTC_STATE_DTERM, CTC_EVENT_IO_ENODEV, ctcm_chx_iofatal }, + + { CTC_STATE_TX, CTC_EVENT_STOP, ctcm_chx_haltio }, + { CTC_STATE_TX, CTC_EVENT_START, fsm_action_nop }, + { CTC_STATE_TX, CTC_EVENT_FINSTAT, ctcmpc_chx_txdone }, + { CTC_STATE_TX, CTC_EVENT_UC_RCRESET, ctcm_chx_fail }, + { CTC_STATE_TX, CTC_EVENT_UC_RSRESET, ctcm_chx_fail }, + { CTC_STATE_TX, CTC_EVENT_TIMER, ctcm_chx_txretry }, + { CTC_STATE_TX, CTC_EVENT_IO_ENODEV, ctcm_chx_iofatal }, + { CTC_STATE_TX, CTC_EVENT_MC_FAIL, ctcm_chx_fail }, + { CTC_STATE_TX, CTC_EVENT_RSWEEP_TIMER, ctcmpc_chx_send_sweep }, + { CTC_STATE_TX, CTC_EVENT_IO_EBUSY, ctcm_chx_fail }, + + { CTC_STATE_RXERR, CTC_EVENT_STOP, ctcm_chx_haltio }, + { CTC_STATE_TXERR, CTC_EVENT_STOP, ctcm_chx_haltio }, + { CTC_STATE_TXERR, CTC_EVENT_IO_ENODEV, ctcm_chx_iofatal }, + { CTC_STATE_TXERR, CTC_EVENT_MC_FAIL, ctcm_chx_fail }, + { CTC_STATE_RXERR, CTC_EVENT_MC_FAIL, ctcm_chx_fail }, +}; + +int mpc_ch_fsm_len = ARRAY_SIZE(ctcmpc_ch_fsm); + +/* + * Actions for interface - statemachine. + */ + +/** + * Startup channels by sending CTC_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 struct net_device * upon call. + */ +static void dev_action_start(fsm_instance *fi, int event, void *arg) +{ + struct net_device *dev = (struct net_device *)arg; + struct ctcm_priv *privptr = dev->priv; + int direction; + + CTCMY_DBF_DEV_NAME(SETUP, dev, ""); + + fsm_deltimer(&privptr->restart_timer); + fsm_newstate(fi, DEV_STATE_STARTWAIT_RXTX); + if (IS_MPC(privptr)) + privptr->mpcg->channels_terminating = 0; + for (direction = READ; direction <= WRITE; direction++) { + struct channel *ch = privptr->channel[direction]; + fsm_event(ch->fsm, CTC_EVENT_START, ch); + } +} + +/** + * Shutdown channels by sending CTC_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 struct net_device * upon call. + */ +static void dev_action_stop(fsm_instance *fi, int event, void *arg) +{ + int direction; + struct net_device *dev = (struct net_device *)arg; + struct ctcm_priv *privptr = (struct ctcm_priv *)dev->priv; + + CTCMY_DBF_DEV_NAME(SETUP, dev, ""); + + fsm_newstate(fi, DEV_STATE_STOPWAIT_RXTX); + for (direction = READ; direction <= WRITE; direction++) { + struct channel *ch = privptr->channel[direction]; + fsm_event(ch->fsm, CTC_EVENT_STOP, ch); + ch->th_seq_num = 0x00; + if (do_debug) + ctcm_pr_debug("ctcm: %s() CH_th_seq= %08x\n", + __FUNCTION__, ch->th_seq_num); + } + if (IS_MPC(privptr)) + fsm_newstate(privptr->mpcg->fsm, MPCG_STATE_RESET); +} + +static void dev_action_restart(fsm_instance *fi, int event, void *arg) +{ + int restart_timer; + struct net_device *dev = (struct net_device *)arg; + struct ctcm_priv *privptr = (struct ctcm_priv *)dev->priv; + + CTCMY_DBF_DEV_NAME(TRACE, dev, ""); + + if (IS_MPC(privptr)) { + ctcm_pr_info("ctcm: %s Restarting Device and " + "MPC Group in 5 seconds\n", + dev->name); + restart_timer = CTCM_TIME_1_SEC; + } else { + ctcm_pr_info("%s: Restarting\n", dev->name); + restart_timer = CTCM_TIME_5_SEC; + } + + dev_action_stop(fi, event, arg); + fsm_event(privptr->fsm, DEV_EVENT_STOP, dev); + if (IS_MPC(privptr)) + fsm_newstate(privptr->mpcg->fsm, MPCG_STATE_RESET); + + /* going back into start sequence too quickly can */ + /* result in the other side becoming unreachable due */ + /* to sense reported when IO is aborted */ + fsm_addtimer(&privptr->restart_timer, restart_timer, + DEV_EVENT_START, dev); +} + +/** + * 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 struct net_device * upon call. + */ +static void dev_action_chup(fsm_instance *fi, int event, void *arg) +{ + struct net_device *dev = (struct net_device *)arg; + struct ctcm_priv *privptr = (struct ctcm_priv *)dev->priv; + + CTCMY_DBF_DEV_NAME(SETUP, dev, ""); + + 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); + ctcm_pr_info("%s: connected with remote side\n", + dev->name); + ctcm_clear_busy(dev); + } + break; + case DEV_STATE_STARTWAIT_TX: + if (event == DEV_EVENT_TXUP) { + fsm_newstate(fi, DEV_STATE_RUNNING); + ctcm_pr_info("%s: connected with remote side\n", + dev->name); + ctcm_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; + } + + if (IS_MPC(privptr)) { + if (event == DEV_EVENT_RXUP) + mpc_channel_action(privptr->channel[READ], + READ, MPC_CHANNEL_ADD); + else + mpc_channel_action(privptr->channel[WRITE], + WRITE, MPC_CHANNEL_ADD); + } +} + +/** + * Called from device 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 struct net_device * upon call. + */ +static void dev_action_chdown(fsm_instance *fi, int event, void *arg) +{ + + struct net_device *dev = (struct net_device *)arg; + struct ctcm_priv *privptr = (struct ctcm_priv *)dev->priv; + + CTCMY_DBF_DEV_NAME(SETUP, dev, ""); + + switch (fsm_getstate(fi)) { + case DEV_STATE_RUNNING: + if (event == DEV_EVENT_TXDOWN) + fsm_newstate(fi, DEV_STATE_STARTWAIT_TX); + else + fsm_newstate(fi, DEV_STATE_STARTWAIT_RX); + break; + case DEV_STATE_STARTWAIT_RX: + if (event == DEV_EVENT_TXDOWN) + fsm_newstate(fi, DEV_STATE_STARTWAIT_RXTX); + break; + case DEV_STATE_STARTWAIT_TX: + if (event == DEV_EVENT_RXDOWN) + fsm_newstate(fi, DEV_STATE_STARTWAIT_RXTX); + break; + case DEV_STATE_STOPWAIT_RXTX: + if (event == DEV_EVENT_TXDOWN) + fsm_newstate(fi, DEV_STATE_STOPWAIT_RX); + else + fsm_newstate(fi, DEV_STATE_STOPWAIT_TX); + break; + case DEV_STATE_STOPWAIT_RX: + if (event == DEV_EVENT_RXDOWN) + fsm_newstate(fi, DEV_STATE_STOPPED); + break; + case DEV_STATE_STOPWAIT_TX: + if (event == DEV_EVENT_TXDOWN) + fsm_newstate(fi, DEV_STATE_STOPPED); + break; + } + if (IS_MPC(privptr)) { + if (event == DEV_EVENT_RXDOWN) + mpc_channel_action(privptr->channel[READ], + READ, MPC_CHANNEL_REMOVE); + else + mpc_channel_action(privptr->channel[WRITE], + WRITE, MPC_CHANNEL_REMOVE); + } +} + +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_RXTX, DEV_EVENT_RESTART, dev_action_restart }, + { 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_RX, DEV_EVENT_RESTART, dev_action_restart }, + { 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_STOPWAIT_TX, DEV_EVENT_RESTART, dev_action_restart }, + { 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_RXTX, DEV_EVENT_RESTART, dev_action_restart }, + { 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_TX, DEV_EVENT_RESTART, dev_action_restart }, + { 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_STARTWAIT_RX, DEV_EVENT_RESTART, dev_action_restart }, + { 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 }, + { DEV_STATE_RUNNING, DEV_EVENT_RESTART, dev_action_restart }, +}; + +int dev_fsm_len = ARRAY_SIZE(dev_fsm); + +/* --- This is the END my friend --- */ + Index: linux-2.6-uschi/drivers/s390/net/ctcm_fsms.h =================================================================== --- /dev/null +++ linux-2.6-uschi/drivers/s390/net/ctcm_fsms.h @@ -0,0 +1,359 @@ +/* + * drivers/s390/net/ctcm_fsms.h + * + * Copyright IBM Corp. 2001, 2007 + * Authors: Fritz Elfert (felfert@millenux.com) + * Peter Tiedemann (ptiedem@de.ibm.com) + * MPC additions : + * Belinda Thompson (belindat@us.ibm.com) + * Andy Richter (richtera@us.ibm.com) + */ +#ifndef _CTCM_FSMS_H_ +#define _CTCM_FSMS_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/bitops.h> + +#include <linux/signal.h> +#include <linux/string.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 <linux/io.h> +#include <asm/ccwdev.h> +#include <asm/ccwgroup.h> +#include <linux/uaccess.h> + +#include <asm/idals.h> + +#include "fsm.h" +#include "cu3088.h" +#include "ctcm_main.h" + +/* + * Definitions for the channel statemachine(s) for ctc and ctcmpc + * + * To allow better kerntyping, prefix-less definitions for channel states + * and channel events have been replaced : + * ch_event... -> ctc_ch_event... + * CH_EVENT... -> CTC_EVENT... + * ch_state... -> ctc_ch_state... + * CH_STATE... -> CTC_STATE... + */ +/* + * Events of the channel statemachine(s) for ctc and ctcmpc + */ +enum ctc_ch_events { + /* + * Events, representing return code of + * I/O operations (ccw_device_start, ccw_device_halt et al.) + */ + CTC_EVENT_IO_SUCCESS, + CTC_EVENT_IO_EBUSY, + CTC_EVENT_IO_ENODEV, + CTC_EVENT_IO_UNKNOWN, + + CTC_EVENT_ATTNBUSY, + CTC_EVENT_ATTN, + CTC_EVENT_BUSY, + /* + * Events, representing unit-check + */ + CTC_EVENT_UC_RCRESET, + CTC_EVENT_UC_RSRESET, + CTC_EVENT_UC_TXTIMEOUT, + CTC_EVENT_UC_TXPARITY, + CTC_EVENT_UC_HWFAIL, + CTC_EVENT_UC_RXPARITY, + CTC_EVENT_UC_ZERO, + CTC_EVENT_UC_UNKNOWN, + /* + * Events, representing subchannel-check + */ + CTC_EVENT_SC_UNKNOWN, + /* + * Events, representing machine checks + */ + CTC_EVENT_MC_FAIL, + CTC_EVENT_MC_GOOD, + /* + * Event, representing normal IRQ + */ + CTC_EVENT_IRQ, + CTC_EVENT_FINSTAT, + /* + * Event, representing timer expiry. + */ + CTC_EVENT_TIMER, + /* + * Events, representing commands from upper levels. + */ + CTC_EVENT_START, + CTC_EVENT_STOP, + CTC_NR_EVENTS, + /* + * additional MPC events + */ + CTC_EVENT_SEND_XID = CTC_NR_EVENTS, + CTC_EVENT_RSWEEP_TIMER, + /* + * MUST be always the last element!! + */ + CTC_MPC_NR_EVENTS, +}; + +/* + * States of the channel statemachine(s) for ctc and ctcmpc. + */ +enum ctc_ch_states { + /* + * Channel not assigned to any device, + * initial state, direction invalid + */ + CTC_STATE_IDLE, + /* + * Channel assigned but not operating + */ + CTC_STATE_STOPPED, + CTC_STATE_STARTWAIT, + CTC_STATE_STARTRETRY, + CTC_STATE_SETUPWAIT, + CTC_STATE_RXINIT, + CTC_STATE_TXINIT, + CTC_STATE_RX, + CTC_STATE_TX, + CTC_STATE_RXIDLE, + CTC_STATE_TXIDLE, + CTC_STATE_RXERR, + CTC_STATE_TXERR, + CTC_STATE_TERM, + CTC_STATE_DTERM, + CTC_STATE_NOTOP, + CTC_NR_STATES, /* MUST be the last element of non-expanded states */ + /* + * additional MPC states + */ + CH_XID0_PENDING = CTC_NR_STATES, + CH_XID0_INPROGRESS, + CH_XID7_PENDING, + CH_XID7_PENDING1, + CH_XID7_PENDING2, + CH_XID7_PENDING3, + CH_XID7_PENDING4, + CTC_MPC_NR_STATES, /* MUST be the last element of expanded mpc states */ +}; + +extern const char *ctc_ch_event_names[]; + +extern const char *ctc_ch_state_names[]; + +void ctcm_ccw_check_rc(struct channel *ch, int rc, char *msg); +void ctcm_purge_skb_queue(struct sk_buff_head *q); +void fsm_action_nop(fsm_instance *fi, int event, void *arg); + +/* + * ----- non-static actions for ctcm channel statemachine ----- + * + */ +void ctcm_chx_txidle(fsm_instance *fi, int event, void *arg); + +/* + * ----- FSM (state/event/action) of the ctcm channel statemachine ----- + */ +extern const fsm_node ch_fsm[]; +extern int ch_fsm_len; + + +/* + * ----- non-static actions for ctcmpc channel statemachine ---- + * + */ +/* shared : +void ctcm_chx_txidle(fsm_instance * fi, int event, void *arg); + */ +void ctcmpc_chx_rxidle(fsm_instance *fi, int event, void *arg); + +/* + * ----- FSM (state/event/action) of the ctcmpc channel statemachine ----- + */ +extern const fsm_node ctcmpc_ch_fsm[]; +extern int mpc_ch_fsm_len; + +/* + * Definitions for the device interface statemachine for ctc and mpc + */ + +/* + * States of the device 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!! + */ + CTCM_NR_DEV_STATES +}; + +extern const char *dev_state_names[]; + +/* + * Events of the device interface statemachine. + * ctcm and ctcmpc + */ +enum dev_events { + DEV_EVENT_START, + DEV_EVENT_STOP, + DEV_EVENT_RXUP, + DEV_EVENT_TXUP, + DEV_EVENT_RXDOWN, + DEV_EVENT_TXDOWN, + DEV_EVENT_RESTART, + /* + * MUST be always the last element!! + */ + CTCM_NR_DEV_EVENTS +}; + +extern const char *dev_event_names[]; + +/* + * Actions for the device interface statemachine. + * ctc and ctcmpc + */ +/* +static void dev_action_start(fsm_instance * fi, int event, void *arg); +static void dev_action_stop(fsm_instance * fi, int event, void *arg); +static void dev_action_restart(fsm_instance *fi, int event, void *arg); +static void dev_action_chup(fsm_instance * fi, int event, void *arg); +static void dev_action_chdown(fsm_instance * fi, int event, void *arg); +*/ + +/* + * The (state/event/action) fsm table of the device interface statemachine. + * ctcm and ctcmpc + */ +extern const fsm_node dev_fsm[]; +extern int dev_fsm_len; + + +/* + * Definitions for the MPC Group statemachine + */ + +/* + * MPC Group Station FSM States + +State Name When In This State +====================== ======================================= +MPCG_STATE_RESET Initial State When Driver Loaded + We receive and send NOTHING + +MPCG_STATE_INOP INOP Received. + Group level non-recoverable error + +MPCG_STATE_READY XID exchanges for at least 1 write and + 1 read channel have completed. + Group is ready for data transfer. + +States from ctc_mpc_alloc_channel +============================================================== +MPCG_STATE_XID2INITW Awaiting XID2(0) Initiation + ATTN from other side will start + XID negotiations. + Y-side protocol only. + +MPCG_STATE_XID2INITX XID2(0) negotiations are in progress. + At least 1, but not all, XID2(0)'s + have been received from partner. + +MPCG_STATE_XID7INITW XID2(0) complete + No XID2(7)'s have yet been received. + XID2(7) negotiations pending. + +MPCG_STATE_XID7INITX XID2(7) negotiations in progress. + At least 1, but not all, XID2(7)'s + have been received from partner. + +MPCG_STATE_XID7INITF XID2(7) negotiations complete. + Transitioning to READY. + +MPCG_STATE_READY Ready for Data Transfer. + + +States from ctc_mpc_establish_connectivity call +============================================================== +MPCG_STATE_XID0IOWAIT Initiating XID2(0) negotiations. + X-side protocol only. + ATTN-BUSY from other side will convert + this to Y-side protocol and the + ctc_mpc_alloc_channel flow will begin. + +MPCG_STATE_XID0IOWAIX XID2(0) negotiations are in progress. + At least 1, but not all, XID2(0)'s + have been received from partner. + +MPCG_STATE_XID7INITI XID2(0) complete + No XID2(7)'s have yet been received. + XID2(7) negotiations pending. + +MPCG_STATE_XID7INITZ XID2(7) negotiations in progress. + At least 1, but not all, XID2(7)'s + have been received from partner. + +MPCG_STATE_XID7INITF XID2(7) negotiations complete. + Transitioning to READY. + +MPCG_STATE_READY Ready for Data Transfer. + +*/ + +enum mpcg_events { + MPCG_EVENT_INOP, + MPCG_EVENT_DISCONC, + MPCG_EVENT_XID0DO, + MPCG_EVENT_XID2, + MPCG_EVENT_XID2DONE, + MPCG_EVENT_XID7DONE, + MPCG_EVENT_TIMER, + MPCG_EVENT_DOIO, + MPCG_NR_EVENTS, +}; + +enum mpcg_states { + MPCG_STATE_RESET, + MPCG_STATE_INOP, + MPCG_STATE_XID2INITW, + MPCG_STATE_XID2INITX, + MPCG_STATE_XID7INITW, + MPCG_STATE_XID7INITX, + MPCG_STATE_XID0IOWAIT, + MPCG_STATE_XID0IOWAIX, + MPCG_STATE_XID7INITI, + MPCG_STATE_XID7INITZ, + MPCG_STATE_XID7INITF, + MPCG_STATE_FLOWC, + MPCG_STATE_READY, + MPCG_NR_STATES, +}; + +#endif +/* --- This is the END my friend --- */ Index: linux-2.6-uschi/drivers/s390/net/ctcm_main.c =================================================================== --- /dev/null +++ linux-2.6-uschi/drivers/s390/net/ctcm_main.c @@ -0,0 +1,1772 @@ +/* + * drivers/s390/net/ctcm_main.c + * + * Copyright IBM Corp. 2001, 2007 + * Author(s): + * Original CTC driver(s): + * Fritz Elfert (felfert@millenux.com) + * Dieter Wellerdiek (wel@de.ibm.com) + * Martin Schwidefsky (schwidefsky@de.ibm.com) + * Denis Joseph Barrow (barrow_dj@yahoo.com) + * Jochen Roehrig (roehrig@de.ibm.com) + * Cornelia Huck <cornelia.huck@de.ibm.com> + * MPC additions: + * Belinda Thompson (belindat@us.ibm.com) + * Andy Richter (richtera@us.ibm.com) + * Revived by: + * Peter Tiedemann (ptiedem@de.ibm.com) + */ + +#undef DEBUG +#undef DEBUGDATA +#undef DEBUGCCW + +#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/bitops.h> + +#include <linux/signal.h> +#include <linux/string.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 <linux/io.h> +#include <asm/ccwdev.h> +#include <asm/ccwgroup.h> +#include <linux/uaccess.h> + +#include <asm/idals.h> + +#include "cu3088.h" +#include "ctcm_fsms.h" +#include "ctcm_main.h" + +/* Some common global variables */ + +/* + * Linked list of all detected channels. + */ +struct channel *channels; + +/** + * 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. + */ +void ctcm_unpack_skb(struct channel *ch, struct sk_buff *pskb) +{ + struct net_device *dev = ch->netdev; + struct ctcm_priv *privptr = (struct ctcm_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; + int skblen; + struct ll_header *header = (struct ll_header *)pskb->data; + + skb_pull(pskb, LL_HEADER_LENGTH); + if ((ch->protocol == CTCM_PROTO_S390) && + (header->type != ETH_P_IP)) { + + if (!(ch->logflags & LOG_FLAG_ILLEGALPKT)) { + /* + * Check packet type only if we stick strictly + * to S/390's protocol of OS390. This only + * supports IP. Otherwise allow any packet + * type. + */ + ctcm_pr_warn("%s Illegal packet type 0x%04x " + "received, dropping\n", + dev->name, header->type); + ch->logflags |= LOG_FLAG_ILLEGALPKT; + } + + privptr->stats.rx_dropped++; + privptr->stats.rx_frame_errors++; + return; + } + pskb->protocol = ntohs(header->type); + if (header->length <= LL_HEADER_LENGTH) { + if (!(ch->logflags & LOG_FLAG_ILLEGALSIZE)) { + ctcm_pr_warn( + "%s Illegal packet size %d " + "received (MTU=%d blocklen=%d), " + "dropping\n", dev->name, header->length, + dev->mtu, len); + ch->logflags |= LOG_FLAG_ILLEGALSIZE; + } + + privptr->stats.rx_dropped++; + privptr->stats.rx_length_errors++; + return; + } + header->length -= LL_HEADER_LENGTH; + len -= LL_HEADER_LENGTH; + if ((header->length > skb_tailroom(pskb)) || + (header->length > len)) { + if (!(ch->logflags & LOG_FLAG_OVERRUN)) { + ctcm_pr_warn( + "%s Illegal packet size %d (beyond the" + " end of received data), dropping\n", + dev->name, header->length); + ch->logflags |= LOG_FLAG_OVERRUN; + } + + privptr->stats.rx_dropped++; + privptr->stats.rx_length_errors++; + return; + } + skb_put(pskb, header->length); + skb_reset_mac_header(pskb); + len -= header->length; + skb = dev_alloc_skb(pskb->len); + if (!skb) { + if (!(ch->logflags & LOG_FLAG_NOMEM)) { + ctcm_pr_warn( + "%s Out of memory in ctcm_unpack_skb\n", + dev->name); + ch->logflags |= LOG_FLAG_NOMEM; + } + privptr->stats.rx_dropped++; + return; + } + skb_copy_from_linear_data(pskb, skb_put(skb, pskb->len), + pskb->len); + skb_reset_mac_header(skb); + skb->dev = pskb->dev; + skb->protocol = pskb->protocol; + pskb->ip_summed = CHECKSUM_UNNECESSARY; + skblen = skb->len; + /* + * reset logflags + */ + ch->logflags = 0; + privptr->stats.rx_packets++; + privptr->stats.rx_bytes += skblen; + netif_rx_ni(skb); + dev->last_rx = jiffies; + if (len > 0) { + skb_pull(pskb, header->length); + if (skb_tailroom(pskb) < LL_HEADER_LENGTH) { + if (!(ch->logflags & LOG_FLAG_OVERRUN)) { + CTCM_DBF_DEV_NAME(TRACE, dev, + "Overrun in ctcm_unpack_skb"); + ch->logflags |= LOG_FLAG_OVERRUN; + } + return; + } + skb_put(pskb, LL_HEADER_LENGTH); + } + } +} + +/** + * Release a specific channel in the channel list. + * + * @param ch Pointer to channel struct to be released. + */ +static void channel_free(struct channel *ch) +{ + CTCM_DBF_TEXT(TRACE, 2, __FUNCTION__); + ch->flags &= ~CHANNEL_FLAGS_INUSE; + fsm_newstate(ch->fsm, CTC_STATE_IDLE); +} + +/** + * Remove a specific channel in the channel list. + * + * @param ch Pointer to channel struct to be released. + */ +static void channel_remove(struct channel *ch) +{ + struct channel **c = &channels; + char chid[CTCM_ID_SIZE+1]; + int ok = 0; + + if (ch == NULL) + return; + else + strncpy(chid, ch->id, CTCM_ID_SIZE); + + channel_free(ch); + while (*c) { + if (*c == ch) { + *c = ch->next; + fsm_deltimer(&ch->timer); + if (IS_MPC(ch)) + fsm_deltimer(&ch->sweep_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_any(ch->trans_skb); + } + if (IS_MPC(ch)) { + tasklet_kill(&ch->ch_tasklet); + tasklet_kill(&ch->ch_disc_tasklet); + kfree(ch->discontact_th); + } + kfree(ch->ccw); + kfree(ch->irb); + kfree(ch); + ok = 1; + break; + } + c = &((*c)->next); + } + + CTCM_DBF_TEXT_(SETUP, CTC_DBF_INFO, "%s(%s) %s", CTCM_FUNTAIL, + chid, ok ? "OK" : "failed"); +} + +/** + * Get a specific channel from the channel list. + * + * @param type Type of channel we are interested in. + * @param id Id 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 struct channel *channel_get(enum channel_types type, + char *id, int direction) +{ + struct channel *ch = channels; + + if (do_debug) { + char buf[64]; + sprintf(buf, "%s(%d, %s, %d)\n", + CTCM_FUNTAIL, type, id, direction); + CTCM_DBF_TEXT(TRACE, CTC_DBF_INFO, buf); + } + while (ch && (strncmp(ch->id, id, CTCM_ID_SIZE) || (ch->type != type))) + ch = ch->next; + if (!ch) { + char buf[64]; + sprintf(buf, "%s(%d, %s, %d) not found in channel list\n", + CTCM_FUNTAIL, type, id, direction); + CTCM_DBF_TEXT(ERROR, CTC_DBF_ERROR, buf); + } 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, CTC_STATE_STOPPED); + } + } + return ch; +} + +static long ctcm_check_irb_error(struct ccw_device *cdev, struct irb *irb) +{ + if (!IS_ERR(irb)) + return 0; + + CTCM_DBF_TEXT_(ERROR, CTC_DBF_WARN, "irb error %ld on device %s\n", + PTR_ERR(irb), cdev->dev.bus_id); + + switch (PTR_ERR(irb)) { + case -EIO: + ctcm_pr_warn("i/o-error on device %s\n", cdev->dev.bus_id); + break; + case -ETIMEDOUT: + ctcm_pr_warn("timeout on device %s\n", cdev->dev.bus_id); + break; + default: + ctcm_pr_warn("unknown error %ld on device %s\n", + PTR_ERR(irb), cdev->dev.bus_id); + } + return PTR_ERR(irb); +} + + +/** + * Check sense of a unit check. + * + * @param ch The channel, the sense code belongs to. + * @param sense The sense code to inspect. + */ +static inline void ccw_unit_check(struct channel *ch, unsigned char sense) +{ + CTCM_DBF_TEXT(TRACE, 5, __FUNCTION__); + if (sense & SNS0_INTERVENTION_REQ) { + if (sense & 0x01) { + ctcm_pr_debug("%s: Interface disc. or Sel. reset " + "(remote)\n", ch->id); + fsm_event(ch->fsm, CTC_EVENT_UC_RCRESET, ch); + } else { + ctcm_pr_debug("%s: System reset (remote)\n", ch->id); + fsm_event(ch->fsm, CTC_EVENT_UC_RSRESET, ch); + } + } else if (sense & SNS0_EQUIPMENT_CHECK) { + if (sense & SNS0_BUS_OUT_CHECK) { + ctcm_pr_warn("%s: Hardware malfunction (remote)\n", + ch->id); + fsm_event(ch->fsm, CTC_EVENT_UC_HWFAIL, ch); + } else { + ctcm_pr_warn("%s: Read-data parity error (remote)\n", + ch->id); + fsm_event(ch->fsm, CTC_EVENT_UC_RXPARITY, ch); + } + } else if (sense & SNS0_BUS_OUT_CHECK) { + if (sense & 0x04) { + ctcm_pr_warn("%s: Data-streaming timeout)\n", ch->id); + fsm_event(ch->fsm, CTC_EVENT_UC_TXTIMEOUT, ch); + } else { + ctcm_pr_warn("%s: Data-transfer parity error\n", + ch->id); + fsm_event(ch->fsm, CTC_EVENT_UC_TXPARITY, ch); + } + } else if (sense & SNS0_CMD_REJECT) { + ctcm_pr_warn("%s: Command reject\n", ch->id); + } else if (sense == 0) { + ctcm_pr_debug("%s: Unit check ZERO\n", ch->id); + fsm_event(ch->fsm, CTC_EVENT_UC_ZERO, ch); + } else { + ctcm_pr_warn("%s: Unit Check with sense code: %02x\n", + ch->id, sense); + fsm_event(ch->fsm, CTC_EVENT_UC_UNKNOWN, ch); + } +} + +int ctcm_ch_alloc_buffer(struct channel *ch) +{ + CTCM_DBF_TEXT(TRACE, 5, __FUNCTION__); + + clear_normalized_cda(&ch->ccw[1]); + ch->trans_skb = __dev_alloc_skb(ch->max_bufsize, GFP_ATOMIC | GFP_DMA); + if (ch->trans_skb == NULL) { + ctcm_pr_warn("%s: Couldn't alloc %s trans_skb\n", + ch->id, + (CHANNEL_DIRECTION(ch->flags) == READ) ? "RX" : "TX"); + return -ENOMEM; + } + + ch->ccw[1].count = ch->max_bufsize; + if (set_normalized_cda(&ch->ccw[1], ch->trans_skb->data)) { + dev_kfree_skb(ch->trans_skb); + ch->trans_skb = NULL; + ctcm_pr_warn("%s: set_normalized_cda for %s " + "trans_skb failed, dropping packets\n", + ch->id, + (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; +} + +/* + * 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.) + */ +int ctcm_open(struct net_device *dev) +{ + struct ctcm_priv *privptr = (struct ctcm_priv *)dev->priv; + + CTCMY_DBF_DEV_NAME(SETUP, dev, ""); + if (!IS_MPC(privptr)) + fsm_event(privptr->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.) + */ +int ctcm_close(struct net_device *dev) +{ + struct ctcm_priv *privptr = (struct ctcm_priv *)dev->priv; + + CTCMY_DBF_DEV_NAME(SETUP, dev, ""); + if (!IS_MPC(privptr)) + fsm_event(privptr->fsm, DEV_EVENT_STOP, dev); + return 0; +} + + +/** + * Transmit a packet. + * This is a helper function for ctcm_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 ctcm_tx(). + * + * @return 0 on success, -ERRNO on failure. (Never fails.) + */ +static int ctcm_transmit_skb(struct channel *ch, struct sk_buff *skb) +{ + unsigned long saveflags; + struct ll_header header; + int rc = 0; + __u16 block_len; + int ccw_idx; + struct sk_buff *nskb; + unsigned long hi; + + /* we need to acquire the lock for testing the state + * otherwise we can have an IRQ changing the state to + * TXIDLE after the test but before acquiring the lock. + */ + spin_lock_irqsave(&ch->collect_lock, saveflags); + if (fsm_getstate(ch->fsm) != CTC_STATE_TXIDLE) { + int l = skb->len + LL_HEADER_LENGTH; + + if (ch->collect_len + l > ch->max_bufsize - 2) { + spin_unlock_irqrestore(&ch->collect_lock, saveflags); + return -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); + goto done; + } + spin_unlock_irqrestore(&ch->collect_lock, saveflags); + /* + * 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; + + /* + * IDAL support in CTCM is broken, so we have to + * care about skb's above 2G ourselves. + */ + hi = ((unsigned long)skb_tail_pointer(skb) + LL_HEADER_LENGTH) >> 31; + if (hi) { + nskb = alloc_skb(skb->len, GFP_ATOMIC | GFP_DMA); + if (!nskb) { + atomic_dec(&skb->users); + skb_pull(skb, LL_HEADER_LENGTH + 2); + ctcm_clear_busy(ch->netdev); + return -ENOMEM; + } else { + memcpy(skb_put(nskb, skb->len), skb->data, skb->len); + atomic_inc(&nskb->users); + atomic_dec(&skb->users); + dev_kfree_skb_irq(skb); + skb = nskb; + } + } + + ch->ccw[4].count = block_len; + if (set_normalized_cda(&ch->ccw[4], skb->data)) { + /* + * idal allocation failed, try via copying to + * trans_skb. trans_skb usually has a pre-allocated + * idal. + */ + if (ctcm_checkalloc_buffer(ch)) { + /* + * Remove our header. It gets added + * again on retransmit. + */ + atomic_dec(&skb->users); + skb_pull(skb, LL_HEADER_LENGTH + 2); + ctcm_clear_busy(ch->netdev); + return -EBUSY; + } + + skb_reset_tail_pointer(ch->trans_skb); + ch->trans_skb->len = 0; + ch->ccw[1].count = skb->len; + skb_copy_from_linear_data(skb, + skb_put(ch->trans_skb, skb->len), 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; + fsm_newstate(ch->fsm, CTC_STATE_TX); + fsm_addtimer(&ch->timer, CTCM_TIME_5_SEC, CTC_EVENT_TIMER, ch); + spin_lock_irqsave(get_ccwdev_lock(ch->cdev), saveflags); + ch->prof.send_stamp = current_kernel_time(); /* xtime */ + rc = ccw_device_start(ch->cdev, &ch->ccw[ccw_idx], + (unsigned long)ch, 0xff, 0); + spin_unlock_irqrestore(get_ccwdev_lock(ch->cdev), saveflags); + if (ccw_idx == 3) + ch->prof.doios_single++; + if (rc != 0) { + fsm_deltimer(&ch->timer); + ctcm_ccw_check_rc(ch, rc, "single skb TX"); + 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) { + struct net_device *dev = ch->netdev; + struct ctcm_priv *privptr = dev->priv; + privptr->stats.tx_packets++; + privptr->stats.tx_bytes += skb->len - LL_HEADER_LENGTH; + } +done: + ctcm_clear_busy(ch->netdev); + return rc; +} + +static void ctcmpc_send_sweep_req(struct channel *rch) +{ + struct net_device *dev = rch->netdev; + struct ctcm_priv *privptr; + struct mpc_group *grpptr; + struct th_sweep *header; + struct sk_buff *sweep_skb; + struct channel *ch; + int rc = 0; + + privptr = (struct ctcm_priv *)dev->priv; + grpptr = privptr->mpcg; + ch = privptr->channel[WRITE]; + + if (do_debug) + MPC_DBF_DEV_NAME(TRACE, dev, ch->id); + + /* sweep processing is not complete until response and request */ + /* has completed for all read channels in group */ + if (grpptr->in_sweep == 0) { + grpptr->in_sweep = 1; + grpptr->sweep_rsp_pend_num = grpptr->active_channels[READ]; + grpptr->sweep_req_pend_num = grpptr->active_channels[READ]; + } + + sweep_skb = __dev_alloc_skb(MPC_BUFSIZE_DEFAULT, GFP_ATOMIC|GFP_DMA); + + if (sweep_skb == NULL) { + printk(KERN_INFO "Couldn't alloc sweep_skb\n"); + rc = -ENOMEM; + goto done; + } + + header = kmalloc(TH_SWEEP_LENGTH, gfp_type()); + + if (!header) { + dev_kfree_skb_any(sweep_skb); + rc = -ENOMEM; + goto done; + } + + header->th.th_seg = 0x00 ; + header->th.th_ch_flag = TH_SWEEP_REQ; /* 0x0f */ + header->th.th_blk_flag = 0x00; + header->th.th_is_xid = 0x00; + header->th.th_seq_num = 0x00; + header->sw.th_last_seq = ch->th_seq_num; + + memcpy(skb_put(sweep_skb, TH_SWEEP_LENGTH), header, TH_SWEEP_LENGTH); + + kfree(header); + + dev->trans_start = jiffies; + skb_queue_tail(&ch->sweep_queue, sweep_skb); + + fsm_addtimer(&ch->sweep_timer, 100, CTC_EVENT_RSWEEP_TIMER, ch); + + return; + +done: + if (rc != 0) { + grpptr->in_sweep = 0; + ctcm_clear_busy(dev); + fsm_event(grpptr->fsm, MPCG_EVENT_INOP, dev); + } + + return; +} + +/* + * MPC mode version of transmit_skb + */ +static int ctcmpc_transmit_skb(struct channel *ch, struct sk_buff *skb) +{ + struct pdu *p_header; + struct net_device *dev = ch->netdev; + struct ctcm_priv *privptr = (struct ctcm_priv *)dev->priv; + struct mpc_group *grpptr = privptr->mpcg; + struct th_header *header; + struct sk_buff *nskb; + int rc = 0; + int ccw_idx; + unsigned long hi; + unsigned long saveflags = 0; /* avoids compiler warning */ + __u16 block_len; + + if (do_debug) + ctcm_pr_debug( + "ctcm enter: %s(): %s cp=%i ch=0x%p id=%s state=%s\n", + __FUNCTION__, dev->name, smp_processor_id(), ch, + ch->id, fsm_getstate_str(ch->fsm)); + + if ((fsm_getstate(ch->fsm) != CTC_STATE_TXIDLE) || grpptr->in_sweep) { + spin_lock_irqsave(&ch->collect_lock, saveflags); + atomic_inc(&skb->users); + p_header = kmalloc(PDU_HEADER_LENGTH, gfp_type()); + + if (!p_header) { + printk(KERN_WARNING "ctcm: OUT OF MEMORY IN %s():" + " Data Lost \n", __FUNCTION__); + + atomic_dec(&skb->users); + dev_kfree_skb_any(skb); + spin_unlock_irqrestore(&ch->collect_lock, saveflags); + fsm_event(privptr->mpcg->fsm, MPCG_EVENT_INOP, dev); + goto done; + } + + p_header->pdu_offset = skb->len; + p_header->pdu_proto = 0x01; + p_header->pdu_flag = 0x00; + if (skb->protocol == ntohs(ETH_P_SNAP)) { + p_header->pdu_flag |= PDU_FIRST | PDU_CNTL; + } else { + p_header->pdu_flag |= PDU_FIRST; + } + p_header->pdu_seq = 0; + memcpy(skb_push(skb, PDU_HEADER_LENGTH), p_header, + PDU_HEADER_LENGTH); + + if (do_debug_data) { + ctcm_pr_debug("ctcm: %s() Putting on collect_q" + " - skb len: %04x \n", __FUNCTION__, skb->len); + ctcm_pr_debug("ctcm: %s() pdu header and data" + " for up to 32 bytes\n", __FUNCTION__); + ctcmpc_dump32((char *)skb->data, skb->len); + } + + skb_queue_tail(&ch->collect_queue, skb); + ch->collect_len += skb->len; + kfree(p_header); + + spin_unlock_irqrestore(&ch->collect_lock, saveflags); + goto done; + } + + /* + * Protect skb against beeing free'd by upper + * layers. + */ + atomic_inc(&skb->users); + + block_len = skb->len + TH_HEADER_LENGTH + PDU_HEADER_LENGTH; + /* + * IDAL support in CTCM is broken, so we have to + * care about skb's above 2G ourselves. + */ + hi = ((unsigned long)skb->tail + TH_HEADER_LENGTH) >> 31; + if (hi) { + nskb = __dev_alloc_skb(skb->len, GFP_ATOMIC | GFP_DMA); + if (!nskb) { + printk(KERN_WARNING "ctcm: %s() OUT OF MEMORY" + "- Data Lost \n", __FUNCTION__); + atomic_dec(&skb->users); + dev_kfree_skb_any(skb); + fsm_event(privptr->mpcg->fsm, MPCG_EVENT_INOP, dev); + goto done; + } else { + memcpy(skb_put(nskb, skb->len), skb->data, skb->len); + atomic_inc(&nskb->users); + atomic_dec(&skb->users); + dev_kfree_skb_irq(skb); + skb = nskb; + } + } + + p_header = kmalloc(PDU_HEADER_LENGTH, gfp_type()); + + if (!p_header) { + printk(KERN_WARNING "ctcm: %s() OUT OF MEMORY" + ": Data Lost \n", __FUNCTION__); + + atomic_dec(&skb->users); + dev_kfree_skb_any(skb); + fsm_event(privptr->mpcg->fsm, MPCG_EVENT_INOP, dev); + goto done; + } + + p_header->pdu_offset = skb->len; + p_header->pdu_proto = 0x01; + p_header->pdu_flag = 0x00; + p_header->pdu_seq = 0; + if (skb->protocol == ntohs(ETH_P_SNAP)) { + p_header->pdu_flag |= PDU_FIRST | PDU_CNTL; + } else { + p_header->pdu_flag |= PDU_FIRST; + } + memcpy(skb_push(skb, PDU_HEADER_LENGTH), p_header, PDU_HEADER_LENGTH); + + kfree(p_header); + + if (ch->collect_len > 0) { + spin_lock_irqsave(&ch->collect_lock, saveflags); + skb_queue_tail(&ch->collect_queue, skb); + ch->collect_len += skb->len; + skb = skb_dequeue(&ch->collect_queue); + ch->collect_len -= skb->len; + spin_unlock_irqrestore(&ch->collect_lock, saveflags); + } + + p_header = (struct pdu *)skb->data; + p_header->pdu_flag |= PDU_LAST; + + ch->prof.txlen += skb->len - PDU_HEADER_LENGTH; + + header = kmalloc(TH_HEADER_LENGTH, gfp_type()); + + if (!header) { + printk(KERN_WARNING "ctcm: %s() OUT OF MEMORY: Data Lost \n", + __FUNCTION__); + atomic_dec(&skb->users); + dev_kfree_skb_any(skb); + fsm_event(privptr->mpcg->fsm, MPCG_EVENT_INOP, dev); + goto done; + } + + header->th_seg = 0x00; + header->th_ch_flag = TH_HAS_PDU; /* Normal data */ + header->th_blk_flag = 0x00; + header->th_is_xid = 0x00; /* Just data here */ + ch->th_seq_num++; + header->th_seq_num = ch->th_seq_num; + + if (do_debug_data) + ctcm_pr_debug("ctcm: %s() ToVTAM_th_seq= %08x\n" , + __FUNCTION__, ch->th_seq_num); + + /* put the TH on the packet */ + memcpy(skb_push(skb, TH_HEADER_LENGTH), header, TH_HEADER_LENGTH); + + kfree(header); + + if (do_debug_data) { + ctcm_pr_debug("ctcm: %s(): skb len: %04x \n", + __FUNCTION__, skb->len); + ctcm_pr_debug("ctcm: %s(): pdu header and data for up to 32 " + "bytes sent to vtam\n", __FUNCTION__); + ctcmpc_dump32((char *)skb->data, skb->len); + } + + ch->ccw[4].count = skb->len; + if (set_normalized_cda(&ch->ccw[4], skb->data)) { + /* + * idal allocation failed, try via copying to + * trans_skb. trans_skb usually has a pre-allocated + * idal. + */ + if (ctcm_checkalloc_buffer(ch)) { + /* + * Remove our header. It gets added + * again on retransmit. + */ + atomic_dec(&skb->users); + dev_kfree_skb_any(skb); + printk(KERN_WARNING "ctcm: %s()OUT OF MEMORY:" + " Data Lost \n", __FUNCTION__); + fsm_event(privptr->mpcg->fsm, MPCG_EVENT_INOP, dev); + goto done; + } + + skb_reset_tail_pointer(ch->trans_skb); + 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; + if (do_debug_data) { + ctcm_pr_debug("ctcm: %s() TRANS skb len: %d \n", + __FUNCTION__, ch->trans_skb->len); + ctcm_pr_debug("ctcm: %s up to 32 bytes of data" + " sent to vtam\n", __FUNCTION__); + ctcmpc_dump32((char *)ch->trans_skb->data, + ch->trans_skb->len); + } + } else { + skb_queue_tail(&ch->io_queue, skb); + ccw_idx = 3; + } + ch->retry = 0; + fsm_newstate(ch->fsm, CTC_STATE_TX); + fsm_addtimer(&ch->timer, CTCM_TIME_5_SEC, CTC_EVENT_TIMER, ch); + + if (do_debug_ccw) + ctcmpc_dumpit((char *)&ch->ccw[ccw_idx], + sizeof(struct ccw1) * 3); + + spin_lock_irqsave(get_ccwdev_lock(ch->cdev), saveflags); + ch->prof.send_stamp = current_kernel_time(); /* xtime */ + rc = ccw_device_start(ch->cdev, &ch->ccw[ccw_idx], + (unsigned long)ch, 0xff, 0); + spin_unlock_irqrestore(get_ccwdev_lock(ch->cdev), saveflags); + if (ccw_idx == 3) + ch->prof.doios_single++; + if (rc != 0) { + fsm_deltimer(&ch->timer); + ctcm_ccw_check_rc(ch, rc, "single skb TX"); + if (ccw_idx == 3) + skb_dequeue_tail(&ch->io_queue); + } else if (ccw_idx == 0) { + privptr->stats.tx_packets++; + privptr->stats.tx_bytes += skb->len - TH_HEADER_LENGTH; + } + if (ch->th_seq_num > 0xf0000000) /* Chose 4Billion at random. */ + ctcmpc_send_sweep_req(ch); + +done: + if (do_debug) + ctcm_pr_debug("ctcm exit: %s %s()\n", dev->name, __FUNCTION__); + 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. + */ +/* first merge version - leaving both functions separated */ +static int ctcm_tx(struct sk_buff *skb, struct net_device *dev) +{ + int rc = 0; + struct ctcm_priv *privptr; + + CTCM_DBF_TEXT(TRACE, 5, __FUNCTION__); + privptr = (struct ctcm_priv *)dev->priv; + + if (skb == NULL) { + ctcm_pr_warn("%s: NULL sk_buff passed\n", dev->name); + privptr->stats.tx_dropped++; + return 0; + } + if (skb_headroom(skb) < (LL_HEADER_LENGTH + 2)) { + ctcm_pr_warn("%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 + * and throw away packet. + */ + if (fsm_getstate(privptr->fsm) != DEV_STATE_RUNNING) { + fsm_event(privptr->fsm, DEV_EVENT_START, dev); + dev_kfree_skb(skb); + privptr->stats.tx_dropped++; + privptr->stats.tx_errors++; + privptr->stats.tx_carrier_errors++; + return 0; + } + + if (ctcm_test_and_set_busy(dev)) + return -EBUSY; + + dev->trans_start = jiffies; + if (ctcm_transmit_skb(privptr->channel[WRITE], skb) != 0) + rc = 1; + return rc; +} + +/* unmerged MPC variant of ctcm_tx */ +static int ctcmpc_tx(struct sk_buff *skb, struct net_device *dev) +{ + int len = 0; + struct ctcm_priv *privptr = NULL; + struct mpc_group *grpptr = NULL; + struct sk_buff *newskb = NULL; + + if (do_debug) + ctcm_pr_debug("ctcmpc enter: %s(): skb:%0lx\n", + __FUNCTION__, (unsigned long)skb); + + CTCM_DBF_TEXT_(MPC_TRACE, CTC_DBF_DEBUG, + "ctcmpc enter: %s(): skb:%0lx\n", + __FUNCTION__, (unsigned long)skb); + + privptr = (struct ctcm_priv *)dev->priv; + grpptr = privptr->mpcg; + /* + * Some sanity checks ... + */ + if (skb == NULL) { + ctcm_pr_warn("ctcmpc: %s: NULL sk_buff passed\n", dev->name); + privptr->stats.tx_dropped++; + goto done; + } + if (skb_headroom(skb) < (TH_HEADER_LENGTH + PDU_HEADER_LENGTH)) { + CTCM_DBF_TEXT_(MPC_TRACE, CTC_DBF_WARN, + "%s: Got sk_buff with head room < %ld bytes\n", + dev->name, TH_HEADER_LENGTH + PDU_HEADER_LENGTH); + + if (do_debug_data) + ctcmpc_dump32((char *)skb->data, skb->len); + + len = skb->len + TH_HEADER_LENGTH + PDU_HEADER_LENGTH; + newskb = __dev_alloc_skb(len, gfp_type() | GFP_DMA); + + if (!newskb) { + printk(KERN_WARNING "ctcmpc: %s() OUT OF MEMORY-" + "Data Lost\n", + __FUNCTION__); + + dev_kfree_skb_any(skb); + privptr->stats.tx_dropped++; + privptr->stats.tx_errors++; + privptr->stats.tx_carrier_errors++; + fsm_event(grpptr->fsm, MPCG_EVENT_INOP, dev); + goto done; + } + newskb->protocol = skb->protocol; + skb_reserve(newskb, TH_HEADER_LENGTH + PDU_HEADER_LENGTH); + memcpy(skb_put(newskb, skb->len), skb->data, skb->len); + dev_kfree_skb_any(skb); + skb = newskb; + } + + /* + * If channels are not running, + * notify anybody about a link failure and throw + * away packet. + */ + if ((fsm_getstate(privptr->fsm) != DEV_STATE_RUNNING) || + (fsm_getstate(grpptr->fsm) < MPCG_STATE_XID2INITW)) { + dev_kfree_skb_any(skb); + printk(KERN_INFO "ctcmpc: %s() DATA RCVD - MPC GROUP " + "NOT ACTIVE - DROPPED\n", + __FUNCTION__); + privptr->stats.tx_dropped++; + privptr->stats.tx_errors++; + privptr->stats.tx_carrier_errors++; + goto done; + } + + if (ctcm_test_and_set_busy(dev)) { + printk(KERN_WARNING "%s:DEVICE ERR - UNRECOVERABLE DATA LOSS\n", + __FUNCTION__); + dev_kfree_skb_any(skb); + privptr->stats.tx_dropped++; + privptr->stats.tx_errors++; + privptr->stats.tx_carrier_errors++; + fsm_event(grpptr->fsm, MPCG_EVENT_INOP, dev); + goto done; + } + + dev->trans_start = jiffies; + if (ctcmpc_transmit_skb(privptr->channel[WRITE], skb) != 0) { + printk(KERN_WARNING "ctcmpc: %s() DEVICE ERROR" + ": Data Lost \n", + __FUNCTION__); + printk(KERN_WARNING "ctcmpc: %s() DEVICE ERROR" + " - UNRECOVERABLE DATA LOSS\n", + __FUNCTION__); + dev_kfree_skb_any(skb); + privptr->stats.tx_dropped++; + privptr->stats.tx_errors++; + privptr->stats.tx_carrier_errors++; + ctcm_clear_busy(dev); + fsm_event(grpptr->fsm, MPCG_EVENT_INOP, dev); + goto done; + } + ctcm_clear_busy(dev); +done: + if (do_debug) + MPC_DBF_DEV_NAME(TRACE, dev, "exit"); + + return 0; /* handle freeing of skb here */ +} + + +/** + * 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 ctcm_change_mtu(struct net_device *dev, int new_mtu) +{ + struct ctcm_priv *privptr; + int max_bufsize; + + CTCM_DBF_TEXT(SETUP, CTC_DBF_INFO, __FUNCTION__); + + if (new_mtu < 576 || new_mtu > 65527) + return -EINVAL; + + privptr = (struct ctcm_priv *)dev->priv; + max_bufsize = privptr->channel[READ]->max_bufsize; + + if (IS_MPC(privptr)) { + if (new_mtu > max_bufsize - TH_HEADER_LENGTH) + return -EINVAL; + dev->hard_header_len = TH_HEADER_LENGTH + PDU_HEADER_LENGTH; + } else { + if (new_mtu > max_bufsize - LL_HEADER_LENGTH - 2) + return -EINVAL; + dev->hard_header_len = LL_HEADER_LENGTH + 2; + } + dev->mtu = new_mtu; + 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 *ctcm_stats(struct net_device *dev) +{ + return &((struct ctcm_priv *)dev->priv)->stats; +} + + +static void ctcm_netdev_unregister(struct net_device *dev) +{ + CTCM_DBF_TEXT(SETUP, CTC_DBF_INFO, __FUNCTION__); + if (!dev) + return; + unregister_netdev(dev); +} + +static int ctcm_netdev_register(struct net_device *dev) +{ + CTCM_DBF_TEXT(SETUP, CTC_DBF_INFO, __FUNCTION__); + return register_netdev(dev); +} + +static void ctcm_free_netdevice(struct net_device *dev) +{ + struct ctcm_priv *privptr; + struct mpc_group *grpptr; + + CTCM_DBF_TEXT(SETUP, CTC_DBF_INFO, __FUNCTION__); + + if (!dev) + return; + privptr = dev->priv; + if (privptr) { + grpptr = privptr->mpcg; + if (grpptr) { + if (grpptr->fsm) + kfree_fsm(grpptr->fsm); + if (grpptr->xid_skb) + dev_kfree_skb(grpptr->xid_skb); + if (grpptr->rcvd_xid_skb) + dev_kfree_skb(grpptr->rcvd_xid_skb); + tasklet_kill(&grpptr->mpc_tasklet2); + kfree(grpptr); + privptr->mpcg = NULL; + } + if (privptr->fsm) { + kfree_fsm(privptr->fsm); + privptr->fsm = NULL; + } + kfree(privptr->xid); + privptr->xid = NULL; + /* + * Note: kfree(privptr); is done in "opposite" function of + * allocator function probe_device which is remove_device. + */ + } +#ifdef MODULE + free_netdev(dev); +#endif +} + +struct mpc_group *ctcmpc_init_mpc_group(struct ctcm_priv *privptr); + +void static ctcm_dev_setup(struct net_device *dev) +{ + dev->open = ctcm_open; + dev->stop = ctcm_close; + dev->get_stats = ctcm_stats; + dev->change_mtu = ctcm_change_mtu; + dev->type = ARPHRD_SLIP; + dev->tx_queue_len = 100; + dev->flags = IFF_POINTOPOINT | IFF_NOARP; +} + +/* + * Initialize everything of the net device except the name and the + * channel structs. + */ +static struct net_device *ctcm_init_netdevice(struct ctcm_priv *privptr) +{ + struct net_device *dev; + struct mpc_group *grpptr; + if (!privptr) + return NULL; + + if (IS_MPC(privptr)) + dev = alloc_netdev(0, MPC_DEVICE_GENE, ctcm_dev_setup); + else + dev = alloc_netdev(0, CTC_DEVICE_GENE, ctcm_dev_setup); + + if (!dev) { + ctcm_pr_err("%s: Out of memory\n", __FUNCTION__); + return NULL; + } + dev->priv = privptr; + privptr->fsm = init_fsm("ctcmdev", dev_state_names, dev_event_names, + CTCM_NR_DEV_STATES, CTCM_NR_DEV_EVENTS, + dev_fsm, dev_fsm_len, GFP_KERNEL); + if (privptr->fsm == NULL) { + CTCMY_DBF_DEV(SETUP, dev, "init_fsm error"); + kfree(dev); + return NULL; + } + fsm_newstate(privptr->fsm, DEV_STATE_STOPPED); + fsm_settimer(privptr->fsm, &privptr->restart_timer); + + if (IS_MPC(privptr)) { + /* MPC Group Initializations */ + grpptr = ctcmpc_init_mpc_group(privptr); + if (grpptr == NULL) { + MPC_DBF_DEV(SETUP, dev, "init_mpc_group error"); + kfree(dev); + return NULL; + } + tasklet_init(&grpptr->mpc_tasklet2, + mpc_group_ready, (unsigned long)dev); + dev->mtu = MPC_BUFSIZE_DEFAULT - + TH_HEADER_LENGTH - PDU_HEADER_LENGTH; + + dev->hard_start_xmit = ctcmpc_tx; + dev->hard_header_len = TH_HEADER_LENGTH + PDU_HEADER_LENGTH; + privptr->buffer_size = MPC_BUFSIZE_DEFAULT; + } else { + dev->mtu = CTCM_BUFSIZE_DEFAULT - LL_HEADER_LENGTH - 2; + dev->hard_start_xmit = ctcm_tx; + dev->hard_header_len = LL_HEADER_LENGTH + 2; + } + + CTCMY_DBF_DEV(SETUP, dev, "finished"); + return dev; +} + +/** + * Main IRQ handler. + * + * @param cdev The ccw_device the interrupt is for. + * @param intparm interruption parameter. + * @param irb interruption response block. + */ +static void ctcm_irq_handler(struct ccw_device *cdev, + unsigned long intparm, struct irb *irb) +{ + struct channel *ch; + struct net_device *dev; + struct ctcm_priv *priv; + struct ccwgroup_device *cgdev; + + CTCM_DBF_TEXT(TRACE, CTC_DBF_DEBUG, __FUNCTION__); + if (ctcm_check_irb_error(cdev, irb)) + return; + + cgdev = dev_get_drvdata(&cdev->dev); + + /* Check for unsolicited interrupts. */ + if (cgdev == NULL) { + ctcm_pr_warn("ctcm: Got unsolicited irq: %s c-%02x d-%02x\n", + cdev->dev.bus_id, irb->scsw.cstat, + irb->scsw.dstat); + return; + } + + priv = dev_get_drvdata(&cgdev->dev); + + /* Try to extract channel from driver data. */ + if (priv->channel[READ]->cdev == cdev) + ch = priv->channel[READ]; + else if (priv->channel[WRITE]->cdev == cdev) + ch = priv->channel[WRITE]; + else { + ctcm_pr_err("ctcm: Can't determine channel for interrupt, " + "device %s\n", cdev->dev.bus_id); + return; + } + + dev = (struct net_device *)(ch->netdev); + if (dev == NULL) { + ctcm_pr_crit("ctcm: %s dev=NULL bus_id=%s, ch=0x%p\n", + __FUNCTION__, cdev->dev.bus_id, ch); + return; + } + + if (do_debug) + ctcm_pr_debug("%s: interrupt for device: %s " + "received c-%02x d-%02x\n", + dev->name, + ch->id, + irb->scsw.cstat, + irb->scsw.dstat); + + /* Copy interruption response block. */ + memcpy(ch->irb, irb, sizeof(struct irb)); + + /* Check for good subchannel return code, otherwise error message */ + if (irb->scsw.cstat) { + fsm_event(ch->fsm, CTC_EVENT_SC_UNKNOWN, ch); + ctcm_pr_warn("%s: subchannel check for dev: %s - %02x %02x\n", + dev->name, ch->id, irb->scsw.cstat, + irb->scsw.dstat); + return; + } + + /* Check the reason-code of a unit check */ + if (irb->scsw.dstat & DEV_STAT_UNIT_CHECK) { + ccw_unit_check(ch, irb->ecw[0]); + return; + } + if (irb->scsw.dstat & DEV_STAT_BUSY) { + if (irb->scsw.dstat & DEV_STAT_ATTENTION) + fsm_event(ch->fsm, CTC_EVENT_ATTNBUSY, ch); + else + fsm_event(ch->fsm, CTC_EVENT_BUSY, ch); + return; + } + if (irb->scsw.dstat & DEV_STAT_ATTENTION) { + fsm_event(ch->fsm, CTC_EVENT_ATTN, ch); + return; + } + if ((irb->scsw.stctl & SCSW_STCTL_SEC_STATUS) || + (irb->scsw.stctl == SCSW_STCTL_STATUS_PEND) || + (irb->scsw.stctl == + (SCSW_STCTL_ALERT_STATUS | SCSW_STCTL_STATUS_PEND))) + fsm_event(ch->fsm, CTC_EVENT_FINSTAT, ch); + else + fsm_event(ch->fsm, CTC_EVENT_IRQ, ch); + +} + +/** + * Add ctcm specific attributes. + * Add ctcm private data. + * + * @param cgdev pointer to ccwgroup_device just added + * + * @returns 0 on success, !0 on failure. + */ +static int ctcm_probe_device(struct ccwgroup_device *cgdev) +{ + struct ctcm_priv *priv; + int rc; + + CTCM_DBF_TEXT_(SETUP, CTC_DBF_INFO, "%s %p", __FUNCTION__, cgdev); + + if (!get_device(&cgdev->dev)) + return -ENODEV; + + priv = kzalloc(sizeof(struct ctcm_priv), GFP_KERNEL); + if (!priv) { + ctcm_pr_err("%s: Out of memory\n", __FUNCTION__); + put_device(&cgdev->dev); + return -ENOMEM; + } + + rc = ctcm_add_files(&cgdev->dev); + if (rc) { + kfree(priv); + put_device(&cgdev->dev); + return rc; + } + priv->buffer_size = CTCM_BUFSIZE_DEFAULT; + cgdev->cdev[0]->handler = ctcm_irq_handler; + cgdev->cdev[1]->handler = ctcm_irq_handler; + dev_set_drvdata(&cgdev->dev, priv); + + return 0; +} + +/** + * Add a new channel to the list of channels. + * Keeps the channel list sorted. + * + * @param cdev The ccw_device to be added. + * @param type The type class of the new channel. + * @param priv Points to the private data of the ccwgroup_device. + * + * @return 0 on success, !0 on error. + */ +static int add_channel(struct ccw_device *cdev, enum channel_types type, + struct ctcm_priv *priv) +{ + struct channel **c = &channels; + struct channel *ch; + int ccw_num; + int rc = 0; + + CTCM_DBF_TEXT(TRACE, 2, __FUNCTION__); + ch = kzalloc(sizeof(struct channel), GFP_KERNEL); + if (ch == NULL) + goto nomem_return; + + ch->protocol = priv->protocol; + if (IS_MPC(priv)) { + ch->discontact_th = (struct th_header *) + kzalloc(TH_HEADER_LENGTH, gfp_type()); + if (ch->discontact_th == NULL) + goto nomem_return; + + ch->discontact_th->th_blk_flag = TH_DISCONTACT; + tasklet_init(&ch->ch_disc_tasklet, + mpc_action_send_discontact, (unsigned long)ch); + + tasklet_init(&ch->ch_tasklet, ctcmpc_bh, (unsigned long)ch); + ch->max_bufsize = (MPC_BUFSIZE_DEFAULT - 35); + ccw_num = 17; + } else + ccw_num = 8; + + ch->ccw = (struct ccw1 *) + kzalloc(ccw_num * sizeof(struct ccw1), GFP_KERNEL | GFP_DMA); + if (ch->ccw == NULL) + goto nomem_return; + + ch->cdev = cdev; + snprintf(ch->id, CTCM_ID_SIZE, "ch-%s", cdev->dev.bus_id); + ch->type = type; + + /** + * "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): + * 6: set extended mode + * 7: nop + * + * ch->ccw[0..5] are initialized in ch_action_start because + * the channel's direction is yet unknown here. + * + * ccws used for xid2 negotiations + * ch-ccw[8-14] need to be used for the XID exchange either + * X side XID2 Processing + * 8: write control + * 9: write th + * 10: write XID + * 11: read th from secondary + * 12: read XID from secondary + * 13: read 4 byte ID + * 14: nop + * Y side XID Processing + * 8: sense + * 9: read th + * 10: read XID + * 11: write th + * 12: write XID + * 13: write 4 byte ID + * 14: nop + * + * ccws used for double noop due to VM timing issues + * which result in unrecoverable Busy on channel + * 15: nop + * 16: nop + */ + ch->ccw[6].cmd_code = CCW_CMD_SET_EXTENDED; + ch->ccw[6].flags = CCW_FLAG_SLI; + + ch->ccw[7].cmd_code = CCW_CMD_NOOP; + ch->ccw[7].flags = CCW_FLAG_SLI; + + if (IS_MPC(priv)) { + ch->ccw[15].cmd_code = CCW_CMD_WRITE; + ch->ccw[15].flags = CCW_FLAG_SLI | CCW_FLAG_CC; + ch->ccw[15].count = TH_HEADER_LENGTH; + ch->ccw[15].cda = virt_to_phys(ch->discontact_th); + + ch->ccw[16].cmd_code = CCW_CMD_NOOP; + ch->ccw[16].flags = CCW_FLAG_SLI; + + ch->fsm = init_fsm(ch->id, ctc_ch_state_names, + ctc_ch_event_names, CTC_MPC_NR_STATES, + CTC_MPC_NR_EVENTS, ctcmpc_ch_fsm, + mpc_ch_fsm_len, GFP_KERNEL); + } else { + ch->fsm = init_fsm(ch->id, ctc_ch_state_names, + ctc_ch_event_names, CTC_NR_STATES, + CTC_NR_EVENTS, ch_fsm, + ch_fsm_len, GFP_KERNEL); + } + if (ch->fsm == NULL) + goto free_return; + + fsm_newstate(ch->fsm, CTC_STATE_IDLE); + + ch->irb = kzalloc(sizeof(struct irb), GFP_KERNEL); + if (ch->irb == NULL) + goto nomem_return; + + while (*c && ctcm_less_than((*c)->id, ch->id)) + c = &(*c)->next; + + if (*c && (!strncmp((*c)->id, ch->id, CTCM_ID_SIZE))) { + CTCM_DBF_TEXT_(SETUP, CTC_DBF_INFO, + "%s (%s) already in list, using old entry", + __FUNCTION__, (*c)->id); + + goto free_return; + } + + spin_lock_init(&ch->collect_lock); + + fsm_settimer(ch->fsm, &ch->timer); + skb_queue_head_init(&ch->io_queue); + skb_queue_head_init(&ch->collect_queue); + + if (IS_MPC(priv)) { + fsm_settimer(ch->fsm, &ch->sweep_timer); + skb_queue_head_init(&ch->sweep_queue); + } + ch->next = *c; + *c = ch; + return 0; + +nomem_return: + ctcm_pr_warn("ctcm: Out of memory in %s\n", __FUNCTION__); + rc = -ENOMEM; + +free_return: /* note that all channel pointers are 0 or valid */ + kfree(ch->ccw); /* TODO: check that again */ + kfree(ch->discontact_th); + kfree_fsm(ch->fsm); + kfree(ch->irb); + kfree(ch); + return rc; +} + +/* + * Return type of a detected device. + */ +static enum channel_types get_channel_type(struct ccw_device_id *id) +{ + enum channel_types type; + type = (enum channel_types)id->driver_info; + + if (type == channel_type_ficon) + type = channel_type_escon; + + return type; +} + +/** + * + * Setup an interface. + * + * @param cgdev Device to be setup. + * + * @returns 0 on success, !0 on failure. + */ +static int ctcm_new_device(struct ccwgroup_device *cgdev) +{ + char read_id[CTCM_ID_SIZE]; + char write_id[CTCM_ID_SIZE]; + int direction; + enum channel_types type; + struct ctcm_priv *privptr; + struct net_device *dev; + int ret; + + CTCM_DBF_TEXT(SETUP, CTC_DBF_INFO, __FUNCTION__); + + privptr = dev_get_drvdata(&cgdev->dev); + if (!privptr) + return -ENODEV; + + type = get_channel_type(&cgdev->cdev[0]->id); + + snprintf(read_id, CTCM_ID_SIZE, "ch-%s", cgdev->cdev[0]->dev.bus_id); + snprintf(write_id, CTCM_ID_SIZE, "ch-%s", cgdev->cdev[1]->dev.bus_id); + + ret = add_channel(cgdev->cdev[0], type, privptr); + if (ret) + return ret; + ret = add_channel(cgdev->cdev[1], type, privptr); + if (ret) + return ret; + + ret = ccw_device_set_online(cgdev->cdev[0]); + if (ret != 0) { + CTCM_DBF_TEXT(SETUP, CTC_DBF_WARN, + "ccw_device_set_online (cdev[0]) failed "); + ctcm_pr_warn("ccw_device_set_online (cdev[0]) failed " + "with ret = %d\n", ret); + } + + ret = ccw_device_set_online(cgdev->cdev[1]); + if (ret != 0) { + CTCM_DBF_TEXT(SETUP, CTC_DBF_WARN, + "ccw_device_set_online (cdev[1]) failed "); + ctcm_pr_warn("ccw_device_set_online (cdev[1]) failed " + "with ret = %d\n", ret); + } + + dev = ctcm_init_netdevice(privptr); + + if (dev == NULL) { + ctcm_pr_warn("ctcm_init_netdevice failed\n"); + goto out; + } + + for (direction = READ; direction <= WRITE; direction++) { + privptr->channel[direction] = + channel_get(type, direction == READ ? read_id : write_id, + direction); + if (privptr->channel[direction] == NULL) { + if (direction == WRITE) + channel_free(privptr->channel[READ]); + ctcm_free_netdevice(dev); + goto out; + } + privptr->channel[direction]->netdev = dev; + privptr->channel[direction]->protocol = privptr->protocol; + privptr->channel[direction]->max_bufsize = privptr->buffer_size; + } + /* sysfs magic */ + SET_NETDEV_DEV(dev, &cgdev->dev); + + if (ctcm_netdev_register(dev) != 0) { + ctcm_free_netdevice(dev); + goto out; + } + + if (ctcm_add_attributes(&cgdev->dev)) { + ctcm_netdev_unregister(dev); +/* dev->priv = NULL; why that ???? */ + ctcm_free_netdevice(dev); + goto out; + } + + strlcpy(privptr->fsm->name, dev->name, sizeof(privptr->fsm->name)); + + CTCM_DBF_TEXT_(SETUP, CTC_DBF_INFO, + "setup(%s) ok : r/w = %s / %s, proto : %d", + dev->name, privptr->channel[READ]->id, + privptr->channel[WRITE]->id, privptr->protocol); + + return 0; +out: + ccw_device_set_offline(cgdev->cdev[1]); + ccw_device_set_offline(cgdev->cdev[0]); + + return -ENODEV; +} + +/** + * Shutdown an interface. + * + * @param cgdev Device to be shut down. + * + * @returns 0 on success, !0 on failure. + */ +static int ctcm_shutdown_device(struct ccwgroup_device *cgdev) +{ + struct ctcm_priv *priv; + struct net_device *dev; + + priv = dev_get_drvdata(&cgdev->dev); + if (!priv) + return -ENODEV; + + if (priv->channel[READ]) { + dev = priv->channel[READ]->netdev; + CTCM_DBF_DEV(SETUP, dev, ""); + /* Close the device */ + ctcm_close(dev); + dev->flags &= ~IFF_RUNNING; + ctcm_remove_attributes(&cgdev->dev); + channel_free(priv->channel[READ]); + } else + dev = NULL; + + if (priv->channel[WRITE]) + channel_free(priv->channel[WRITE]); + + if (dev) { + ctcm_netdev_unregister(dev); +/* dev->priv = NULL; why that ??? */ + ctcm_free_netdevice(dev); + } + + if (priv->fsm) + kfree_fsm(priv->fsm); + + ccw_device_set_offline(cgdev->cdev[1]); + ccw_device_set_offline(cgdev->cdev[0]); + + if (priv->channel[READ]) + channel_remove(priv->channel[READ]); + if (priv->channel[WRITE]) + channel_remove(priv->channel[WRITE]); + priv->channel[READ] = priv->channel[WRITE] = NULL; + + return 0; + +} + + +static void ctcm_remove_device(struct ccwgroup_device *cgdev) +{ + struct ctcm_priv *priv; + + CTCM_DBF_TEXT(SETUP, CTC_DBF_ERROR, __FUNCTION__); + + priv = dev_get_drvdata(&cgdev->dev); + if (!priv) + return; + if (cgdev->state == CCWGROUP_ONLINE) + ctcm_shutdown_device(cgdev); + ctcm_remove_files(&cgdev->dev); + dev_set_drvdata(&cgdev->dev, NULL); + kfree(priv); + put_device(&cgdev->dev); +} + +static struct ccwgroup_driver ctcm_group_driver = { + .owner = THIS_MODULE, + .name = CTC_DRIVER_NAME, + .max_slaves = 2, + .driver_id = 0xC3E3C3D4, /* CTCM */ + .probe = ctcm_probe_device, + .remove = ctcm_remove_device, + .set_online = ctcm_new_device, + .set_offline = ctcm_shutdown_device, +}; + + +/* + * Module related routines + */ + +/* + * 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. + */ +static void __exit ctcm_exit(void) +{ + unregister_cu3088_discipline(&ctcm_group_driver); + ctcm_unregister_dbf_views(); + ctcm_pr_info("CTCM driver unloaded\n"); +} + +/* + * Print Banner. + */ +static void print_banner(void) +{ + printk(KERN_INFO "CTCM driver initialized\n"); +} + +/** + * Initialize module. + * This is called just after the module is loaded. + * + * @return 0 on success, !0 on error. + */ +static int __init ctcm_init(void) +{ + int ret; + + channels = NULL; + + ret = ctcm_register_dbf_views(); + if (ret) { + ctcm_pr_crit("ctcm_init failed with ctcm_register_dbf_views " + "rc = %d\n", ret); + return ret; + } + ret = register_cu3088_discipline(&ctcm_group_driver); + if (ret) { + ctcm_unregister_dbf_views(); + ctcm_pr_crit("ctcm_init failed with register_cu3088_discipline " + "(rc = %d)\n", ret); + return ret; + } + print_banner(); + return ret; +} + +module_init(ctcm_init); +module_exit(ctcm_exit); + +MODULE_AUTHOR("Peter Tiedemann <ptiedem@de.ibm.com>"); +MODULE_DESCRIPTION("Network driver for S/390 CTC + CTCMPC (SNA)"); +MODULE_LICENSE("GPL"); + Index: linux-2.6-uschi/drivers/s390/net/ctcm_main.h =================================================================== --- /dev/null +++ linux-2.6-uschi/drivers/s390/net/ctcm_main.h @@ -0,0 +1,287 @@ +/* + * drivers/s390/net/ctcm_main.h + * + * Copyright IBM Corp. 2001, 2007 + * Authors: Fritz Elfert (felfert@millenux.com) + * Peter Tiedemann (ptiedem@de.ibm.com) + */ + +#ifndef _CTCM_MAIN_H_ +#define _CTCM_MAIN_H_ + +#include <asm/ccwdev.h> +#include <asm/ccwgroup.h> + +#include <linux/skbuff.h> +#include <linux/netdevice.h> + +#include "fsm.h" +#include "cu3088.h" +#include "ctcm_dbug.h" +#include "ctcm_mpc.h" + +#define CTC_DRIVER_NAME "ctcm" +#define CTC_DEVICE_NAME "ctc" +#define CTC_DEVICE_GENE "ctc%d" +#define MPC_DEVICE_NAME "mpc" +#define MPC_DEVICE_GENE "mpc%d" + +#define CHANNEL_FLAGS_READ 0 +#define CHANNEL_FLAGS_WRITE 1 +#define CHANNEL_FLAGS_INUSE 2 +#define CHANNEL_FLAGS_BUFSIZE_CHANGED 4 +#define CHANNEL_FLAGS_FAILED 8 +#define CHANNEL_FLAGS_WAITIRQ 16 +#define CHANNEL_FLAGS_RWMASK 1 +#define CHANNEL_DIRECTION(f) (f & CHANNEL_FLAGS_RWMASK) + +#define LOG_FLAG_ILLEGALPKT 1 +#define LOG_FLAG_ILLEGALSIZE 2 +#define LOG_FLAG_OVERRUN 4 +#define LOG_FLAG_NOMEM 8 + +#define ctcm_pr_debug(fmt, arg...) printk(KERN_DEBUG fmt, ##arg) +#define ctcm_pr_info(fmt, arg...) printk(KERN_INFO fmt, ##arg) +#define ctcm_pr_notice(fmt, arg...) printk(KERN_NOTICE fmt, ##arg) +#define ctcm_pr_warn(fmt, arg...) printk(KERN_WARNING fmt, ##arg) +#define ctcm_pr_emerg(fmt, arg...) printk(KERN_EMERG fmt, ##arg) +#define ctcm_pr_err(fmt, arg...) printk(KERN_ERR fmt, ##arg) +#define ctcm_pr_crit(fmt, arg...) printk(KERN_CRIT fmt, ##arg) + +/* + * CCW commands, used in this driver. + */ +#define CCW_CMD_WRITE 0x01 +#define CCW_CMD_READ 0x02 +#define CCW_CMD_NOOP 0x03 +#define CCW_CMD_TIC 0x08 +#define CCW_CMD_SENSE_CMD 0x14 +#define CCW_CMD_WRITE_CTL 0x17 +#define CCW_CMD_SET_EXTENDED 0xc3 +#define CCW_CMD_PREPARE 0xe3 + +#define CTCM_PROTO_S390 0 +#define CTCM_PROTO_LINUX 1 +#define CTCM_PROTO_LINUX_TTY 2 +#define CTCM_PROTO_OS390 3 +#define CTCM_PROTO_MPC 4 +#define CTCM_PROTO_MAX 4 + +#define CTCM_BUFSIZE_LIMIT 65535 +#define CTCM_BUFSIZE_DEFAULT 32768 +#define MPC_BUFSIZE_DEFAULT CTCM_BUFSIZE_LIMIT + +#define CTCM_TIME_1_SEC 1000 +#define CTCM_TIME_5_SEC 5000 +#define CTCM_TIME_10_SEC 10000 + +#define CTCM_INITIAL_BLOCKLEN 2 + +#define READ 0 +#define WRITE 1 + +#define CTCM_ID_SIZE BUS_ID_SIZE+3 + +struct ctcm_profile { + unsigned long maxmulti; + unsigned long maxcqueue; + unsigned long doios_single; + unsigned long doios_multi; + unsigned long txlen; + unsigned long tx_time; + struct timespec send_stamp; +}; + +/* + * Definition of one channel + */ +struct channel { + struct channel *next; + char id[CTCM_ID_SIZE]; + struct ccw_device *cdev; + /* + * Type of this channel. + * CTC/A or Escon for valid channels. + */ + enum channel_types type; + /* + * Misc. flags. See CHANNEL_FLAGS_... below + */ + __u32 flags; + __u16 protocol; /* protocol of this channel (4 = MPC) */ + /* + * I/O and irq related stuff + */ + struct ccw1 *ccw; + struct irb *irb; + /* + * RX/TX buffer size + */ + int max_bufsize; + struct sk_buff *trans_skb; /* transmit/receive buffer */ + struct sk_buff_head io_queue; /* universal I/O queue */ + struct tasklet_struct ch_tasklet; /* MPC ONLY */ + /* + * 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; + /* MPC ONLY section begin */ + __u32 th_seq_num; /* SNA TH seq number */ + __u8 th_seg; + __u32 pdu_seq; + struct sk_buff *xid_skb; + char *xid_skb_data; + struct th_header *xid_th; + struct xid2 *xid; + char *xid_id; + struct th_header *rcvd_xid_th; + struct xid2 *rcvd_xid; + char *rcvd_xid_id; + __u8 in_mpcgroup; + fsm_timer sweep_timer; + struct sk_buff_head sweep_queue; + struct th_header *discontact_th; + struct tasklet_struct ch_disc_tasklet; + /* MPC ONLY section end */ + + int retry; /* retry counter for misc. operations */ + fsm_instance *fsm; /* finite state machine of this channel */ + struct net_device *netdev; /* corresponding net_device */ + struct ctcm_profile prof; + unsigned char *trans_skb_data; + __u16 logflags; +}; + +struct ctcm_priv { + struct net_device_stats stats; + unsigned long tbusy; + + /* The MPC group struct of this interface */ + struct mpc_group *mpcg; /* MPC only */ + struct xid2 *xid; /* MPC only */ + + /* The finite state machine of this interface */ + fsm_instance *fsm; + + /* The protocol of this device */ + __u16 protocol; + + /* Timer for restarting after I/O Errors */ + fsm_timer restart_timer; + + int buffer_size; /* ctc only */ + + struct channel *channel[2]; +}; + +int ctcm_open(struct net_device *dev); +int ctcm_close(struct net_device *dev); + +/* + * prototypes for non-static sysfs functions + */ +int ctcm_add_attributes(struct device *dev); +void ctcm_remove_attributes(struct device *dev); +int ctcm_add_files(struct device *dev); +void ctcm_remove_files(struct device *dev); + +/* + * Compatibility macros for busy handling + * of network devices. + */ +static inline void ctcm_clear_busy_do(struct net_device *dev) +{ + clear_bit(0, &(((struct ctcm_priv *)dev->priv)->tbusy)); + netif_wake_queue(dev); +} + +static inline void ctcm_clear_busy(struct net_device *dev) +{ + struct mpc_group *grpptr; + grpptr = ((struct ctcm_priv *)dev->priv)->mpcg; + + if (!(grpptr && grpptr->in_sweep)) + ctcm_clear_busy_do(dev); +} + + +static inline int ctcm_test_and_set_busy(struct net_device *dev) +{ + netif_stop_queue(dev); + return test_and_set_bit(0, &(((struct ctcm_priv *)dev->priv)->tbusy)); +} + +extern int loglevel; +extern struct channel *channels; + +void ctcm_unpack_skb(struct channel *ch, struct sk_buff *pskb); + +/* + * Functions related to setup and device detection. + */ + +static inline int ctcm_less_than(char *id1, char *id2) +{ + unsigned long dev1, dev2; + + id1 = id1 + 5; + id2 = id2 + 5; + + dev1 = simple_strtoul(id1, &id1, 16); + dev2 = simple_strtoul(id2, &id2, 16); + + return (dev1 < dev2); +} + +int ctcm_ch_alloc_buffer(struct channel *ch); + +static inline int ctcm_checkalloc_buffer(struct channel *ch) +{ + if (ch->trans_skb == NULL) + return ctcm_ch_alloc_buffer(ch); + if (ch->flags & CHANNEL_FLAGS_BUFSIZE_CHANGED) { + dev_kfree_skb(ch->trans_skb); + return ctcm_ch_alloc_buffer(ch); + } + return 0; +} + +struct mpc_group *ctcmpc_init_mpc_group(struct ctcm_priv *privptr); + +/* test if protocol attribute (of struct ctcm_priv or struct channel) + * has MPC protocol setting. Type is not checked + */ +#define IS_MPC(p) ((p)->protocol == CTCM_PROTO_MPC) + +/* test if struct ctcm_priv of struct net_device has MPC protocol setting */ +#define IS_MPCDEV(d) IS_MPC((struct ctcm_priv *)d->priv) + +static inline gfp_t gfp_type(void) +{ + return in_interrupt() ? GFP_ATOMIC : GFP_KERNEL; +} + +/* + * Definition of our link level header. + */ +struct ll_header { + __u16 length; + __u16 type; + __u16 unused; +}; +#define LL_HEADER_LENGTH (sizeof(struct ll_header)) + +#endif Index: linux-2.6-uschi/drivers/s390/net/ctcm_mpc.c =================================================================== --- /dev/null +++ linux-2.6-uschi/drivers/s390/net/ctcm_mpc.c @@ -0,0 +1,2467 @@ +/* + * drivers/s390/net/ctcm_mpc.c + * + * Copyright IBM Corp. 2004, 2007 + * Authors: Belinda Thompson (belindat@us.ibm.com) + * Andy Richter (richtera@us.ibm.com) + * Peter Tiedemann (ptiedem@de.ibm.com) + */ + +/* + This module exports functions to be used by CCS: + EXPORT_SYMBOL(ctc_mpc_alloc_channel); + EXPORT_SYMBOL(ctc_mpc_establish_connectivity); + EXPORT_SYMBOL(ctc_mpc_dealloc_ch); + EXPORT_SYMBOL(ctc_mpc_flow_control); +*/ + +#undef DEBUG +#undef DEBUGDATA +#undef DEBUGCCW + +#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 <linux/netdevice.h> +#include <net/dst.h> + +#include <linux/io.h> /* instead of <asm/io.h> ok ? */ +#include <asm/ccwdev.h> +#include <asm/ccwgroup.h> +#include <linux/bitops.h> /* instead of <asm/bitops.h> ok ? */ +#include <linux/uaccess.h> /* instead of <asm/uaccess.h> ok ? */ +#include <linux/wait.h> +#include <linux/moduleparam.h> +#include <asm/idals.h> + +#include "cu3088.h" +#include "ctcm_mpc.h" +#include "ctcm_main.h" +#include "ctcm_fsms.h" + +static const struct xid2 init_xid = { + .xid2_type_id = XID_FM2, + .xid2_len = 0x45, + .xid2_adj_id = 0, + .xid2_rlen = 0x31, + .xid2_resv1 = 0, + .xid2_flag1 = 0, + .xid2_fmtt = 0, + .xid2_flag4 = 0x80, + .xid2_resv2 = 0, + .xid2_tgnum = 0, + .xid2_sender_id = 0, + .xid2_flag2 = 0, + .xid2_option = XID2_0, + .xid2_resv3 = "\x00", + .xid2_resv4 = 0, + .xid2_dlc_type = XID2_READ_SIDE, + .xid2_resv5 = 0, + .xid2_mpc_flag = 0, + .xid2_resv6 = 0, + .xid2_buf_len = (MPC_BUFSIZE_DEFAULT - 35), +}; + +static const struct th_header thnorm = { + .th_seg = 0x00, + .th_ch_flag = TH_IS_XID, + .th_blk_flag = TH_DATA_IS_XID, + .th_is_xid = 0x01, + .th_seq_num = 0x00000000, +}; + +static const struct th_header thdummy = { + .th_seg = 0x00, + .th_ch_flag = 0x00, + .th_blk_flag = TH_DATA_IS_XID, + .th_is_xid = 0x01, + .th_seq_num = 0x00000000, +}; + +/* + * Definition of one MPC group + */ + +/* + * Compatibility macros for busy handling + * of network devices. + */ + +static void ctcmpc_unpack_skb(struct channel *ch, struct sk_buff *pskb); + +/* + * MPC Group state machine actions (static prototypes) + */ +static void mpc_action_go_ready(fsm_instance *fsm, int event, void *arg); +static void mpc_action_go_inop(fsm_instance *fi, int event, void *arg); +static void mpc_action_timeout(fsm_instance *fi, int event, void *arg); +static int mpc_validate_xid(struct mpcg_info *mpcginfo); +static void mpc_action_yside_xid(fsm_instance *fsm, int event, void *arg); +static void mpc_action_doxid0(fsm_instance *fsm, int event, void *arg); +static void mpc_action_doxid7(fsm_instance *fsm, int event, void *arg); +static void mpc_action_xside_xid(fsm_instance *fsm, int event, void *arg); +static void mpc_action_rcvd_xid0(fsm_instance *fsm, int event, void *arg); +static void mpc_action_rcvd_xid7(fsm_instance *fsm, int event, void *arg); + +#ifdef DEBUGDATA +/*-------------------------------------------------------------------* +* Dump buffer format * +* * +*--------------------------------------------------------------------*/ +void ctcmpc_dumpit(char *buf, int len) +{ + __u32 ct, sw, rm, dup; + char *ptr, *rptr; + char tbuf[82], tdup[82]; + #if (UTS_MACHINE == s390x) + char addr[22]; + #else + char addr[12]; + #endif + char boff[12]; + char bhex[82], duphex[82]; + char basc[40]; + + sw = 0; + rptr = ptr = buf; + rm = 16; + duphex[0] = 0x00; + dup = 0; + + for (ct = 0; ct < len; ct++, ptr++, rptr++) { + if (sw == 0) { + #if (UTS_MACHINE == s390x) + sprintf(addr, "%16.16lx", (unsigned long)rptr); + #else + sprintf(addr, "%8.8X", (__u32)rptr); + #endif + + sprintf(boff, "%4.4X", (__u32)ct); + bhex[0] = '\0'; + basc[0] = '\0'; + } + if ((sw == 4) || (sw == 12)) + strcat(bhex, " "); + if (sw == 8) + strcat(bhex, " "); + + #if (UTS_MACHINE == s390x) + sprintf(tbuf, "%2.2lX", (unsigned long)*ptr); + #else + sprintf(tbuf, "%2.2X", (__u32)*ptr); + #endif + + tbuf[2] = '\0'; + strcat(bhex, tbuf); + if ((0 != isprint(*ptr)) && (*ptr >= 0x20)) + basc[sw] = *ptr; + else + basc[sw] = '.'; + + basc[sw+1] = '\0'; + sw++; + rm--; + if (sw == 16) { + if ((strcmp(duphex, bhex)) != 0) { + if (dup != 0) { + sprintf(tdup, "Duplicate as above " + "to %s", addr); + printk(KERN_INFO " " + " --- %s ---\n", tdup); + } + printk(KERN_INFO " %s (+%s) : %s [%s]\n", + addr, boff, bhex, basc); + dup = 0; + strcpy(duphex, bhex); + } else + dup++; + + sw = 0; + rm = 16; + } + } /* endfor */ + + if (sw != 0) { + for ( ; rm > 0; rm--, sw++) { + if ((sw == 4) || (sw == 12)) + strcat(bhex, " "); + if (sw == 8) + strcat(bhex, " "); + strcat(bhex, " "); + strcat(basc, " "); + } + if (dup != 0) { + sprintf(tdup, "Duplicate as above to %s", addr); + printk(KERN_INFO " " + " --- %s ---\n", tdup); + } + printk(KERN_INFO " %s (+%s) : %s [%s]\n", + addr, boff, bhex, basc); + } else { + if (dup >= 1) { + sprintf(tdup, "Duplicate as above to %s", addr); + printk(KERN_INFO " " + " --- %s ---\n", tdup); + } + if (dup != 0) { + printk(KERN_INFO " %s (+%s) : %s [%s]\n", + addr, boff, bhex, basc); + } + } + + return; + +} /* end of ctcmpc_dumpit */ +#endif + +#ifdef DEBUGDATA +/* + * 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. + */ +void ctcmpc_dump_skb(struct sk_buff *skb, int offset) +{ + unsigned char *p = skb->data; + struct th_header *header; + struct pdu *pheader; + int bl = skb->len; + int i; + + if (p == NULL) + return; + + p += offset; + header = (struct th_header *)p; + + printk(KERN_INFO "dump:\n"); + printk(KERN_INFO "skb len=%d \n", skb->len); + if (skb->len > 2) { + switch (header->th_ch_flag) { + case TH_HAS_PDU: + break; + case 0x00: + case TH_IS_XID: + if ((header->th_blk_flag == TH_DATA_IS_XID) && + (header->th_is_xid == 0x01)) + goto dumpth; + case TH_SWEEP_REQ: + goto dumpth; + case TH_SWEEP_RESP: + goto dumpth; + default: + break; + } + + pheader = (struct pdu *)p; + printk(KERN_INFO "pdu->offset: %d hex: %04x\n", + pheader->pdu_offset, pheader->pdu_offset); + printk(KERN_INFO "pdu->flag : %02x\n", pheader->pdu_flag); + printk(KERN_INFO "pdu->proto : %02x\n", pheader->pdu_proto); + printk(KERN_INFO "pdu->seq : %02x\n", pheader->pdu_seq); + goto dumpdata; + +dumpth: + printk(KERN_INFO "th->seg : %02x\n", header->th_seg); + printk(KERN_INFO "th->ch : %02x\n", header->th_ch_flag); + printk(KERN_INFO "th->blk_flag: %02x\n", header->th_blk_flag); + printk(KERN_INFO "th->type : %s\n", + (header->th_is_xid) ? "DATA" : "XID"); + printk(KERN_INFO "th->seqnum : %04x\n", header->th_seq_num); + + } +dumpdata: + if (bl > 32) + bl = 32; + printk(KERN_INFO "data: "); + for (i = 0; i < bl; i++) + printk(KERN_INFO "%02x%s", *p++, (i % 16) ? " " : "\n<7>"); + printk(KERN_INFO "\n"); +} +#endif + +/* + * ctc_mpc_alloc_channel + * (exported interface) + * + * Device Initialization : + * ACTPATH driven IO operations + */ +int ctc_mpc_alloc_channel(int port_num, void (*callback)(int, int)) +{ + char device[20]; + struct net_device *dev; + struct mpc_group *grpptr; + struct ctcm_priv *privptr; + + ctcm_pr_debug("ctcmpc enter: %s()\n", __FUNCTION__); + + sprintf(device, "%s%i", MPC_DEVICE_NAME, port_num); + dev = __dev_get_by_name(&init_net, device); + + if (dev == NULL) { + printk(KERN_INFO "ctc_mpc_alloc_channel %s dev=NULL\n", device); + return 1; + } + + privptr = (struct ctcm_priv *)dev->priv; + grpptr = privptr->mpcg; + if (!grpptr) + return 1; + + grpptr->allochanfunc = callback; + grpptr->port_num = port_num; + grpptr->port_persist = 1; + + ctcm_pr_debug("ctcmpc: %s called for device %s state=%s\n", + __FUNCTION__, + dev->name, + fsm_getstate_str(grpptr->fsm)); + + switch (fsm_getstate(grpptr->fsm)) { + case MPCG_STATE_INOP: + /* Group is in the process of terminating */ + grpptr->alloc_called = 1; + break; + case MPCG_STATE_RESET: + /* MPC Group will transition to state */ + /* MPCG_STATE_XID2INITW iff the minimum number */ + /* of 1 read and 1 write channel have successfully*/ + /* activated */ + /*fsm_newstate(grpptr->fsm, MPCG_STATE_XID2INITW);*/ + if (callback) + grpptr->send_qllc_disc = 1; + case MPCG_STATE_XID0IOWAIT: + fsm_deltimer(&grpptr->timer); + grpptr->outstanding_xid2 = 0; + grpptr->outstanding_xid7 = 0; + grpptr->outstanding_xid7_p2 = 0; + grpptr->saved_xid2 = NULL; + if (callback) + ctcm_open(dev); + fsm_event(((struct ctcm_priv *)dev->priv)->fsm, + DEV_EVENT_START, dev); + break; + case MPCG_STATE_READY: + /* XID exchanges completed after PORT was activated */ + /* Link station already active */ + /* Maybe timing issue...retry callback */ + grpptr->allocchan_callback_retries++; + if (grpptr->allocchan_callback_retries < 4) { + if (grpptr->allochanfunc) + grpptr->allochanfunc(grpptr->port_num, + grpptr->group_max_buflen); + } else { + /* there are problems...bail out */ + /* there may be a state mismatch so restart */ + grpptr->port_persist = 1; + fsm_event(grpptr->fsm, MPCG_EVENT_INOP, dev); + grpptr->allocchan_callback_retries = 0; + } + break; + default: + return 0; + + } + + ctcm_pr_debug("ctcmpc exit: %s()\n", __FUNCTION__); + return 0; +} +EXPORT_SYMBOL(ctc_mpc_alloc_channel); + +/* + * ctc_mpc_establish_connectivity + * (exported interface) + */ +void ctc_mpc_establish_connectivity(int port_num, + void (*callback)(int, int, int)) +{ + char device[20]; + struct net_device *dev; + struct mpc_group *grpptr; + struct ctcm_priv *privptr; + struct channel *rch, *wch; + + ctcm_pr_debug("ctcmpc enter: %s()\n", __FUNCTION__); + + sprintf(device, "%s%i", MPC_DEVICE_NAME, port_num); + dev = __dev_get_by_name(&init_net, device); + + if (dev == NULL) { + printk(KERN_INFO "ctc_mpc_establish_connectivity " + "%s dev=NULL\n", device); + return; + } + privptr = (struct ctcm_priv *)dev->priv; + rch = privptr->channel[READ]; + wch = privptr->channel[WRITE]; + + grpptr = privptr->mpcg; + + ctcm_pr_debug("ctcmpc: %s() called for device %s state=%s\n", + __FUNCTION__, dev->name, + fsm_getstate_str(grpptr->fsm)); + + grpptr->estconnfunc = callback; + grpptr->port_num = port_num; + + switch (fsm_getstate(grpptr->fsm)) { + case MPCG_STATE_READY: + /* XID exchanges completed after PORT was activated */ + /* Link station already active */ + /* Maybe timing issue...retry callback */ + fsm_deltimer(&grpptr->timer); + grpptr->estconn_callback_retries++; + if (grpptr->estconn_callback_retries < 4) { + if (grpptr->estconnfunc) { + grpptr->estconnfunc(grpptr->port_num, 0, + grpptr->group_max_buflen); + grpptr->estconnfunc = NULL; + } + } else { + /* there are problems...bail out */ + fsm_event(grpptr->fsm, MPCG_EVENT_INOP, dev); + grpptr->estconn_callback_retries = 0; + } + break; + case MPCG_STATE_INOP: + case MPCG_STATE_RESET: + /* MPC Group is not ready to start XID - min num of */ + /* 1 read and 1 write channel have not been acquired*/ + printk(KERN_WARNING "ctcmpc: %s() REJECTED ACTIVE XID Req" + "uest - Channel Pair is not Active\n", __FUNCTION__); + if (grpptr->estconnfunc) { + grpptr->estconnfunc(grpptr->port_num, -1, 0); + grpptr->estconnfunc = NULL; + } + break; + case MPCG_STATE_XID2INITW: + /* alloc channel was called but no XID exchange */ + /* has occurred. initiate xside XID exchange */ + /* make sure yside XID0 processing has not started */ + if ((fsm_getstate(rch->fsm) > CH_XID0_PENDING) || + (fsm_getstate(wch->fsm) > CH_XID0_PENDING)) { + printk(KERN_WARNING "mpc: %s() ABORT ACTIVE XID" + " Request- PASSIVE XID in process\n" + , __FUNCTION__); + break; + } + grpptr->send_qllc_disc = 1; + fsm_newstate(grpptr->fsm, MPCG_STATE_XID0IOWAIT); + fsm_deltimer(&grpptr->timer); + fsm_addtimer(&grpptr->timer, MPC_XID_TIMEOUT_VALUE, + MPCG_EVENT_TIMER, dev); + grpptr->outstanding_xid7 = 0; + grpptr->outstanding_xid7_p2 = 0; + grpptr->saved_xid2 = NULL; + if ((rch->in_mpcgroup) && + (fsm_getstate(rch->fsm) == CH_XID0_PENDING)) + fsm_event(grpptr->fsm, MPCG_EVENT_XID0DO, rch); + else { + printk(KERN_WARNING "mpc: %s() Unable to start" + " ACTIVE XID0 on read channel\n", + __FUNCTION__); + if (grpptr->estconnfunc) { + grpptr->estconnfunc(grpptr->port_num, -1, 0); + grpptr->estconnfunc = NULL; + } + fsm_deltimer(&grpptr->timer); + goto done; + } + if ((wch->in_mpcgroup) && + (fsm_getstate(wch->fsm) == CH_XID0_PENDING)) + fsm_event(grpptr->fsm, MPCG_EVENT_XID0DO, wch); + else { + printk(KERN_WARNING "mpc: %s() Unable to start" + " ACTIVE XID0 on write channel\n", + __FUNCTION__); + if (grpptr->estconnfunc) { + grpptr->estconnfunc(grpptr->port_num, -1, 0); + grpptr->estconnfunc = NULL; + } + fsm_deltimer(&grpptr->timer); + goto done; + } + break; + case MPCG_STATE_XID0IOWAIT: + /* already in active XID negotiations */ + default: + break; + } + +done: + ctcm_pr_debug("ctcmpc exit: %s()\n", __FUNCTION__); + return; +} +EXPORT_SYMBOL(ctc_mpc_establish_connectivity); + +/* + * ctc_mpc_dealloc_ch + * (exported interface) + */ +void ctc_mpc_dealloc_ch(int port_num) +{ + struct net_device *dev; + char device[20]; + struct ctcm_priv *privptr; + struct mpc_group *grpptr; + + ctcm_pr_debug("ctcmpc enter: %s()\n", __FUNCTION__); + sprintf(device, "%s%i", MPC_DEVICE_NAME, port_num); + dev = __dev_get_by_name(&init_net, device); + + if (dev == NULL) { + printk(KERN_INFO "%s() %s dev=NULL\n", __FUNCTION__, device); + goto done; + } + + ctcm_pr_debug("ctcmpc:%s %s() called for device %s refcount=%d\n", + dev->name, __FUNCTION__, + dev->name, atomic_read(&dev->refcnt)); + + privptr = (struct ctcm_priv *)dev->priv; + if (privptr == NULL) { + printk(KERN_INFO "%s() %s privptr=NULL\n", + __FUNCTION__, device); + goto done; + } + fsm_deltimer(&privptr->restart_timer); + + grpptr = privptr->mpcg; + if (grpptr == NULL) { + printk(KERN_INFO "%s() %s dev=NULL\n", __FUNCTION__, device); + goto done; + } + grpptr->channels_terminating = 0; + + fsm_deltimer(&grpptr->timer); + + grpptr->allochanfunc = NULL; + grpptr->estconnfunc = NULL; + grpptr->port_persist = 0; + grpptr->send_qllc_disc = 0; + fsm_event(grpptr->fsm, MPCG_EVENT_INOP, dev); + + ctcm_close(dev); +done: + ctcm_pr_debug("ctcmpc exit: %s()\n", __FUNCTION__); + return; +} +EXPORT_SYMBOL(ctc_mpc_dealloc_ch); + +/* + * ctc_mpc_flow_control + * (exported interface) + */ +void ctc_mpc_flow_control(int port_num, int flowc) +{ + char device[20]; + struct ctcm_priv *privptr; + struct mpc_group *grpptr; + struct net_device *dev; + struct channel *rch; + int mpcg_state; + + ctcm_pr_debug("ctcmpc enter: %s() %i\n", __FUNCTION__, flowc); + + sprintf(device, "%s%i", MPC_DEVICE_NAME, port_num); + dev = __dev_get_by_name(&init_net, device); + + if (dev == NULL) { + printk(KERN_INFO "ctc_mpc_flow_control %s dev=NULL\n", device); + return; + } + + ctcm_pr_debug("ctcmpc: %s %s called \n", dev->name, __FUNCTION__); + + privptr = (struct ctcm_priv *)dev->priv; + if (privptr == NULL) { + printk(KERN_INFO "ctcmpc:%s() %s privptr=NULL\n", + __FUNCTION__, device); + return; + } + grpptr = privptr->mpcg; + rch = privptr->channel[READ]; + + mpcg_state = fsm_getstate(grpptr->fsm); + switch (flowc) { + case 1: + if (mpcg_state == MPCG_STATE_FLOWC) + break; + if (mpcg_state == MPCG_STATE_READY) { + if (grpptr->flow_off_called == 1) + grpptr->flow_off_called = 0; + else + fsm_newstate(grpptr->fsm, MPCG_STATE_FLOWC); + break; + } + break; + case 0: + if (mpcg_state == MPCG_STATE_FLOWC) { + fsm_newstate(grpptr->fsm, MPCG_STATE_READY); + /* ensure any data that has accumulated */ + /* on the io_queue will now be sen t */ + tasklet_schedule(&rch->ch_tasklet); + } + /* possible race condition */ + if (mpcg_state == MPCG_STATE_READY) { + grpptr->flow_off_called = 1; + break; + } + break; + } + + ctcm_pr_debug("ctcmpc exit: %s() %i\n", __FUNCTION__, flowc); +} +EXPORT_SYMBOL(ctc_mpc_flow_control); + +static int mpc_send_qllc_discontact(struct net_device *); + +/* + * helper function of ctcmpc_unpack_skb +*/ +static void mpc_rcvd_sweep_resp(struct mpcg_info *mpcginfo) +{ + struct channel *rch = mpcginfo->ch; + struct net_device *dev = rch->netdev; + struct ctcm_priv *privptr = (struct ctcm_priv *)dev->priv; + struct mpc_group *grpptr = privptr->mpcg; + struct channel *ch = privptr->channel[WRITE]; + + if (do_debug) + ctcm_pr_debug("ctcmpc enter: %s(): ch=0x%p id=%s\n", + __FUNCTION__, ch, ch->id); + + if (do_debug_data) + ctcmpc_dumpit((char *)mpcginfo->sweep, TH_SWEEP_LENGTH); + + grpptr->sweep_rsp_pend_num--; + + if ((grpptr->sweep_req_pend_num == 0) && + (grpptr->sweep_rsp_pend_num == 0)) { + fsm_deltimer(&ch->sweep_timer); + grpptr->in_sweep = 0; + rch->th_seq_num = 0x00; + ch->th_seq_num = 0x00; + ctcm_clear_busy_do(dev); + } + + kfree(mpcginfo); + + return; + +} + +/* + * helper function of mpc_rcvd_sweep_req + * which is a helper of ctcmpc_unpack_skb + */ +static void ctcmpc_send_sweep_resp(struct channel *rch) +{ + struct net_device *dev = rch->netdev; + struct ctcm_priv *privptr = (struct ctcm_priv *)dev->priv; + struct mpc_group *grpptr = privptr->mpcg; + int rc = 0; + struct th_sweep *header; + struct sk_buff *sweep_skb; + struct channel *ch = privptr->channel[WRITE]; + + if (do_debug) + ctcm_pr_debug("ctcmpc exit : %s(): ch=0x%p id=%s\n", + __FUNCTION__, rch, rch->id); + + sweep_skb = __dev_alloc_skb(MPC_BUFSIZE_DEFAULT, + GFP_ATOMIC|GFP_DMA); + if (sweep_skb == NULL) { + printk(KERN_INFO "Couldn't alloc sweep_skb\n"); + rc = -ENOMEM; + goto done; + } + + header = (struct th_sweep *) + kmalloc(sizeof(struct th_sweep), gfp_type()); + + if (!header) { + dev_kfree_skb_any(sweep_skb); + rc = -ENOMEM; + goto done; + } + + header->th.th_seg = 0x00 ; + header->th.th_ch_flag = TH_SWEEP_RESP; + header->th.th_blk_flag = 0x00; + header->th.th_is_xid = 0x00; + header->th.th_seq_num = 0x00; + header->sw.th_last_seq = ch->th_seq_num; + + memcpy(skb_put(sweep_skb, TH_SWEEP_LENGTH), header, TH_SWEEP_LENGTH); + + kfree(header); + + dev->trans_start = jiffies; + skb_queue_tail(&ch->sweep_queue, sweep_skb); + + fsm_addtimer(&ch->sweep_timer, 100, CTC_EVENT_RSWEEP_TIMER, ch); + + return; + +done: + if (rc != 0) { + grpptr->in_sweep = 0; + ctcm_clear_busy_do(dev); + fsm_event(grpptr->fsm, MPCG_EVENT_INOP, dev); + } + + return; +} + +/* + * helper function of ctcmpc_unpack_skb + */ +static void mpc_rcvd_sweep_req(struct mpcg_info *mpcginfo) +{ + struct channel *rch = mpcginfo->ch; + struct net_device *dev = rch->netdev; + struct ctcm_priv *privptr = (struct ctcm_priv *)dev->priv; + struct mpc_group *grpptr = privptr->mpcg; + struct channel *ch = privptr->channel[WRITE]; + + if (do_debug) + CTCM_DBF_TEXT_(MPC_TRACE, CTC_DBF_DEBUG, + " %s(): ch=0x%p id=%s\n", __FUNCTION__, ch, ch->id); + + if (grpptr->in_sweep == 0) { + grpptr->in_sweep = 1; + ctcm_test_and_set_busy(dev); + grpptr->sweep_req_pend_num = grpptr->active_channels[READ]; + grpptr->sweep_rsp_pend_num = grpptr->active_channels[READ]; + } + + if (do_debug_data) + ctcmpc_dumpit((char *)mpcginfo->sweep, TH_SWEEP_LENGTH); + + grpptr->sweep_req_pend_num--; + ctcmpc_send_sweep_resp(ch); + kfree(mpcginfo); + return; +} + +/* + * MPC Group Station FSM definitions + */ +static const char *mpcg_event_names[] = { + [MPCG_EVENT_INOP] = "INOP Condition", + [MPCG_EVENT_DISCONC] = "Discontact Received", + [MPCG_EVENT_XID0DO] = "Channel Active - Start XID", + [MPCG_EVENT_XID2] = "XID2 Received", + [MPCG_EVENT_XID2DONE] = "XID0 Complete", + [MPCG_EVENT_XID7DONE] = "XID7 Complete", + [MPCG_EVENT_TIMER] = "XID Setup Timer", + [MPCG_EVENT_DOIO] = "XID DoIO", +}; + +static const char *mpcg_state_names[] = { + [MPCG_STATE_RESET] = "Reset", + [MPCG_STATE_INOP] = "INOP", + [MPCG_STATE_XID2INITW] = "Passive XID- XID0 Pending Start", + [MPCG_STATE_XID2INITX] = "Passive XID- XID0 Pending Complete", + [MPCG_STATE_XID7INITW] = "Passive XID- XID7 Pending P1 Start", + [MPCG_STATE_XID7INITX] = "Passive XID- XID7 Pending P2 Complete", + [MPCG_STATE_XID0IOWAIT] = "Active XID- XID0 Pending Start", + [MPCG_STATE_XID0IOWAIX] = "Active XID- XID0 Pending Complete", + [MPCG_STATE_XID7INITI] = "Active XID- XID7 Pending Start", + [MPCG_STATE_XID7INITZ] = "Active XID- XID7 Pending Complete ", + [MPCG_STATE_XID7INITF] = "XID - XID7 Complete ", + [MPCG_STATE_FLOWC] = "FLOW CONTROL ON", + [MPCG_STATE_READY] = "READY", +}; + +/* + * The MPC Group Station FSM + * 22 events + */ +static const fsm_node mpcg_fsm[] = { + { MPCG_STATE_RESET, MPCG_EVENT_INOP, mpc_action_go_inop }, + { MPCG_STATE_INOP, MPCG_EVENT_INOP, fsm_action_nop }, + { MPCG_STATE_FLOWC, MPCG_EVENT_INOP, mpc_action_go_inop }, + + { MPCG_STATE_READY, MPCG_EVENT_DISCONC, mpc_action_discontact }, + { MPCG_STATE_READY, MPCG_EVENT_INOP, mpc_action_go_inop }, + + { MPCG_STATE_XID2INITW, MPCG_EVENT_XID0DO, mpc_action_doxid0 }, + { MPCG_STATE_XID2INITW, MPCG_EVENT_XID2, mpc_action_rcvd_xid0 }, + { MPCG_STATE_XID2INITW, MPCG_EVENT_INOP, mpc_action_go_inop }, + { MPCG_STATE_XID2INITW, MPCG_EVENT_TIMER, mpc_action_timeout }, + { MPCG_STATE_XID2INITW, MPCG_EVENT_DOIO, mpc_action_yside_xid }, + + { MPCG_STATE_XID2INITX, MPCG_EVENT_XID0DO, mpc_action_doxid0 }, + { MPCG_STATE_XID2INITX, MPCG_EVENT_XID2, mpc_action_rcvd_xid0 }, + { MPCG_STATE_XID2INITX, MPCG_EVENT_INOP, mpc_action_go_inop }, + { MPCG_STATE_XID2INITX, MPCG_EVENT_TIMER, mpc_action_timeout }, + { MPCG_STATE_XID2INITX, MPCG_EVENT_DOIO, mpc_action_yside_xid }, + + { MPCG_STATE_XID7INITW, MPCG_EVENT_XID2DONE, mpc_action_doxid7 }, + { MPCG_STATE_XID7INITW, MPCG_EVENT_DISCONC, mpc_action_discontact }, + { MPCG_STATE_XID7INITW, MPCG_EVENT_XID2, mpc_action_rcvd_xid7 }, + { MPCG_STATE_XID7INITW, MPCG_EVENT_INOP, mpc_action_go_inop }, + { MPCG_STATE_XID7INITW, MPCG_EVENT_TIMER, mpc_action_timeout }, + { MPCG_STATE_XID7INITW, MPCG_EVENT_XID7DONE, mpc_action_doxid7 }, + { MPCG_STATE_XID7INITW, MPCG_EVENT_DOIO, mpc_action_yside_xid }, + + { MPCG_STATE_XID7INITX, MPCG_EVENT_DISCONC, mpc_action_discontact }, + { MPCG_STATE_XID7INITX, MPCG_EVENT_XID2, mpc_action_rcvd_xid7 }, + { MPCG_STATE_XID7INITX, MPCG_EVENT_INOP, mpc_action_go_inop }, + { MPCG_STATE_XID7INITX, MPCG_EVENT_XID7DONE, mpc_action_doxid7 }, + { MPCG_STATE_XID7INITX, MPCG_EVENT_TIMER, mpc_action_timeout }, + { MPCG_STATE_XID7INITX, MPCG_EVENT_DOIO, mpc_action_yside_xid }, + + { MPCG_STATE_XID0IOWAIT, MPCG_EVENT_XID0DO, mpc_action_doxid0 }, + { MPCG_STATE_XID0IOWAIT, MPCG_EVENT_DISCONC, mpc_action_discontact }, + { MPCG_STATE_XID0IOWAIT, MPCG_EVENT_XID2, mpc_action_rcvd_xid0 }, + { MPCG_STATE_XID0IOWAIT, MPCG_EVENT_INOP, mpc_action_go_inop }, + { MPCG_STATE_XID0IOWAIT, MPCG_EVENT_TIMER, mpc_action_timeout }, + { MPCG_STATE_XID0IOWAIT, MPCG_EVENT_DOIO, mpc_action_xside_xid }, + + { MPCG_STATE_XID0IOWAIX, MPCG_EVENT_XID0DO, mpc_action_doxid0 }, + { MPCG_STATE_XID0IOWAIX, MPCG_EVENT_DISCONC, mpc_action_discontact }, + { MPCG_STATE_XID0IOWAIX, MPCG_EVENT_XID2, mpc_action_rcvd_xid0 }, + { MPCG_STATE_XID0IOWAIX, MPCG_EVENT_INOP, mpc_action_go_inop }, + { MPCG_STATE_XID0IOWAIX, MPCG_EVENT_TIMER, mpc_action_timeout }, + { MPCG_STATE_XID0IOWAIX, MPCG_EVENT_DOIO, mpc_action_xside_xid }, + + { MPCG_STATE_XID7INITI, MPCG_EVENT_XID2DONE, mpc_action_doxid7 }, + { MPCG_STATE_XID7INITI, MPCG_EVENT_XID2, mpc_action_rcvd_xid7 }, + { MPCG_STATE_XID7INITI, MPCG_EVENT_DISCONC, mpc_action_discontact }, + { MPCG_STATE_XID7INITI, MPCG_EVENT_INOP, mpc_action_go_inop }, + { MPCG_STATE_XID7INITI, MPCG_EVENT_TIMER, mpc_action_timeout }, + { MPCG_STATE_XID7INITI, MPCG_EVENT_XID7DONE, mpc_action_doxid7 }, + { MPCG_STATE_XID7INITI, MPCG_EVENT_DOIO, mpc_action_xside_xid }, + + { MPCG_STATE_XID7INITZ, MPCG_EVENT_XID2, mpc_action_rcvd_xid7 }, + { MPCG_STATE_XID7INITZ, MPCG_EVENT_XID7DONE, mpc_action_doxid7 }, + { MPCG_STATE_XID7INITZ, MPCG_EVENT_DISCONC, mpc_action_discontact }, + { MPCG_STATE_XID7INITZ, MPCG_EVENT_INOP, mpc_action_go_inop }, + { MPCG_STATE_XID7INITZ, MPCG_EVENT_TIMER, mpc_action_timeout }, + { MPCG_STATE_XID7INITZ, MPCG_EVENT_DOIO, mpc_action_xside_xid }, + + { MPCG_STATE_XID7INITF, MPCG_EVENT_INOP, mpc_action_go_inop }, + { MPCG_STATE_XID7INITF, MPCG_EVENT_XID7DONE, mpc_action_go_ready }, +}; + +static int mpcg_fsm_len = ARRAY_SIZE(mpcg_fsm); + +/* + * MPC Group Station FSM action + * CTCM_PROTO_MPC only + */ +static void mpc_action_go_ready(fsm_instance *fsm, int event, void *arg) +{ + struct net_device *dev = (struct net_device *)arg; + struct ctcm_priv *privptr = NULL; + struct mpc_group *grpptr = NULL; + + if (dev == NULL) { + printk(KERN_INFO "%s() dev=NULL\n", __FUNCTION__); + return; + } + + ctcm_pr_debug("ctcmpc enter: %s %s()\n", dev->name, __FUNCTION__); + + privptr = (struct ctcm_priv *)dev->priv; + if (privptr == NULL) { + printk(KERN_INFO "%s() privptr=NULL\n", __FUNCTION__); + return; + } + + grpptr = privptr->mpcg; + if (grpptr == NULL) { + printk(KERN_INFO "%s() grpptr=NULL\n", __FUNCTION__); + return; + } + + fsm_deltimer(&grpptr->timer); + + if (grpptr->saved_xid2->xid2_flag2 == 0x40) { + privptr->xid->xid2_flag2 = 0x00; + if (grpptr->estconnfunc) { + grpptr->estconnfunc(grpptr->port_num, 1, + grpptr->group_max_buflen); + grpptr->estconnfunc = NULL; + } else if (grpptr->allochanfunc) + grpptr->send_qllc_disc = 1; + goto done; + } + + grpptr->port_persist = 1; + grpptr->out_of_sequence = 0; + grpptr->estconn_called = 0; + + tasklet_hi_schedule(&grpptr->mpc_tasklet2); + + ctcm_pr_debug("ctcmpc exit: %s %s()\n", dev->name, __FUNCTION__); + return; + +done: + fsm_event(grpptr->fsm, MPCG_EVENT_INOP, dev); + + + ctcm_pr_info("ctcmpc: %s()failure occurred\n", __FUNCTION__); +} + +/* + * helper of ctcm_init_netdevice + * CTCM_PROTO_MPC only + */ +void mpc_group_ready(unsigned long adev) +{ + struct net_device *dev = (struct net_device *)adev; + struct ctcm_priv *privptr = NULL; + struct mpc_group *grpptr = NULL; + struct channel *ch = NULL; + + + ctcm_pr_debug("ctcmpc enter: %s()\n", __FUNCTION__); + + if (dev == NULL) { + printk(KERN_INFO "%s() dev=NULL\n", __FUNCTION__); + return; + } + + privptr = (struct ctcm_priv *)dev->priv; + if (privptr == NULL) { + printk(KERN_INFO "%s() privptr=NULL\n", __FUNCTION__); + return; + } + + grpptr = privptr->mpcg; + if (grpptr == NULL) { + printk(KERN_INFO "ctcmpc:%s() grpptr=NULL\n", __FUNCTION__); + return; + } + + printk(KERN_NOTICE "ctcmpc: %s GROUP TRANSITIONED TO READY" + " maxbuf:%d\n", + dev->name, grpptr->group_max_buflen); + + fsm_newstate(grpptr->fsm, MPCG_STATE_READY); + + /* Put up a read on the channel */ + ch = privptr->channel[READ]; + ch->pdu_seq = 0; + if (do_debug_data) + ctcm_pr_debug("ctcmpc: %s() ToDCM_pdu_seq= %08x\n" , + __FUNCTION__, ch->pdu_seq); + + ctcmpc_chx_rxidle(ch->fsm, CTC_EVENT_START, ch); + /* Put the write channel in idle state */ + ch = privptr->channel[WRITE]; + if (ch->collect_len > 0) { + spin_lock(&ch->collect_lock); + ctcm_purge_skb_queue(&ch->collect_queue); + ch->collect_len = 0; + spin_unlock(&ch->collect_lock); + } + ctcm_chx_txidle(ch->fsm, CTC_EVENT_START, ch); + + ctcm_clear_busy(dev); + + if (grpptr->estconnfunc) { + grpptr->estconnfunc(grpptr->port_num, 0, + grpptr->group_max_buflen); + grpptr->estconnfunc = NULL; + } else + if (grpptr->allochanfunc) + grpptr->allochanfunc(grpptr->port_num, + grpptr->group_max_buflen); + + grpptr->send_qllc_disc = 1; + grpptr->changed_side = 0; + + ctcm_pr_debug("ctcmpc exit: %s()\n", __FUNCTION__); + return; + +} + +/* + * Increment the MPC Group Active Channel Counts + * helper of dev_action (called from channel fsm) + */ +int mpc_channel_action(struct channel *ch, int direction, int action) +{ + struct net_device *dev = ch->netdev; + struct ctcm_priv *privptr; + struct mpc_group *grpptr = NULL; + int rc = 0; + + if (do_debug) + ctcm_pr_debug("ctcmpc enter: %s(): ch=0x%p id=%s\n", + __FUNCTION__, ch, ch->id); + + if (dev == NULL) { + printk(KERN_INFO "ctcmpc_channel_action %i dev=NULL\n", + action); + rc = 1; + goto done; + } + + privptr = (struct ctcm_priv *)dev->priv; + if (privptr == NULL) { + printk(KERN_INFO + "ctcmpc_channel_action%i privptr=NULL, dev=%s\n", + action, dev->name); + rc = 2; + goto done; + } + + grpptr = privptr->mpcg; + + if (grpptr == NULL) { + printk(KERN_INFO "ctcmpc: %s()%i mpcgroup=NULL, dev=%s\n", + __FUNCTION__, action, dev->name); + rc = 3; + goto done; + } + + ctcm_pr_info( + "ctcmpc: %s() %i(): Grp:%s total_channel_paths=%i " + "active_channels read=%i, write=%i\n", + __FUNCTION__, + action, + fsm_getstate_str(grpptr->fsm), + grpptr->num_channel_paths, + grpptr->active_channels[READ], + grpptr->active_channels[WRITE]); + + if ((action == MPC_CHANNEL_ADD) && (ch->in_mpcgroup == 0)) { + grpptr->num_channel_paths++; + grpptr->active_channels[direction]++; + grpptr->outstanding_xid2++; + ch->in_mpcgroup = 1; + + if (ch->xid_skb != NULL) + dev_kfree_skb_any(ch->xid_skb); + + ch->xid_skb = __dev_alloc_skb(MPC_BUFSIZE_DEFAULT, + GFP_ATOMIC | GFP_DMA); + if (ch->xid_skb == NULL) { + printk(KERN_INFO "ctcmpc: %s()" + "Couldn't alloc ch xid_skb\n", __FUNCTION__); + fsm_event(grpptr->fsm, MPCG_EVENT_INOP, dev); + return 1; + } + ch->xid_skb_data = ch->xid_skb->data; + ch->xid_th = (struct th_header *)ch->xid_skb->data; + skb_put(ch->xid_skb, TH_HEADER_LENGTH); + ch->xid = (struct xid2 *)skb_tail_pointer(ch->xid_skb); + skb_put(ch->xid_skb, XID2_LENGTH); + ch->xid_id = skb_tail_pointer(ch->xid_skb); + ch->xid_skb->data = ch->xid_skb_data; + skb_reset_tail_pointer(ch->xid_skb); + ch->xid_skb->len = 0; + + memcpy(skb_put(ch->xid_skb, grpptr->xid_skb->len), + grpptr->xid_skb->data, + grpptr->xid_skb->len); + + ch->xid->xid2_dlc_type = ((CHANNEL_DIRECTION(ch->flags) == READ) + ? XID2_READ_SIDE : XID2_WRITE_SIDE); + + if (CHANNEL_DIRECTION(ch->flags) == WRITE) + ch->xid->xid2_buf_len = 0x00; + + ch->xid_skb->data = ch->xid_skb_data; + skb_reset_tail_pointer(ch->xid_skb); + ch->xid_skb->len = 0; + + fsm_newstate(ch->fsm, CH_XID0_PENDING); + + if ((grpptr->active_channels[READ] > 0) && + (grpptr->active_channels[WRITE] > 0) && + (fsm_getstate(grpptr->fsm) < MPCG_STATE_XID2INITW)) { + fsm_newstate(grpptr->fsm, MPCG_STATE_XID2INITW); + printk(KERN_NOTICE "ctcmpc: %s MPC GROUP " + "CHANNELS ACTIVE\n", dev->name); + } + } else if ((action == MPC_CHANNEL_REMOVE) && + (ch->in_mpcgroup == 1)) { + ch->in_mpcgroup = 0; + grpptr->num_channel_paths--; + grpptr->active_channels[direction]--; + + if (ch->xid_skb != NULL) + dev_kfree_skb_any(ch->xid_skb); + ch->xid_skb = NULL; + + if (grpptr->channels_terminating) + goto done; + + if (((grpptr->active_channels[READ] == 0) && + (grpptr->active_channels[WRITE] > 0)) + || ((grpptr->active_channels[WRITE] == 0) && + (grpptr->active_channels[READ] > 0))) + fsm_event(grpptr->fsm, MPCG_EVENT_INOP, dev); + } + +done: + + if (do_debug) { + ctcm_pr_debug( + "ctcmpc: %s() %i Grp:%s ttl_chan_paths=%i " + "active_chans read=%i, write=%i\n", + __FUNCTION__, + action, + fsm_getstate_str(grpptr->fsm), + grpptr->num_channel_paths, + grpptr->active_channels[READ], + grpptr->active_channels[WRITE]); + + ctcm_pr_debug("ctcmpc exit : %s(): ch=0x%p id=%s\n", + __FUNCTION__, ch, ch->id); + } + return rc; + +} + +/** + * Unpack a just received skb and hand it over to + * upper layers. + * special MPC version of unpack_skb. + * + * @param ch The channel where this skb has been received. + * @param pskb The received skb. + */ +static void ctcmpc_unpack_skb(struct channel *ch, struct sk_buff *pskb) +{ + struct net_device *dev = ch->netdev; + struct ctcm_priv *privptr = (struct ctcm_priv *)dev->priv; + struct mpc_group *grpptr = privptr->mpcg; + struct pdu *curr_pdu; + struct mpcg_info *mpcginfo; + struct th_header *header = NULL; + struct th_sweep *sweep = NULL; + int pdu_last_seen = 0; + __u32 new_len; + struct sk_buff *skb; + int skblen; + int sendrc = 0; + + if (do_debug) + ctcm_pr_debug("ctcmpc enter: %s() %s cp:%i ch:%s\n", + __FUNCTION__, dev->name, smp_processor_id(), ch->id); + + header = (struct th_header *)pskb->data; + if ((header->th_seg == 0) && + (header->th_ch_flag == 0) && + (header->th_blk_flag == 0) && + (header->th_seq_num == 0)) + /* nothing for us */ goto done; + + if (do_debug_data) { + ctcm_pr_debug("ctcmpc: %s() th_header\n", __FUNCTION__); + ctcmpc_dumpit((char *)header, TH_HEADER_LENGTH); + ctcm_pr_debug("ctcmpc: %s() pskb len: %04x \n", + __FUNCTION__, pskb->len); + } + + pskb->dev = dev; + pskb->ip_summed = CHECKSUM_UNNECESSARY; + skb_pull(pskb, TH_HEADER_LENGTH); + + if (likely(header->th_ch_flag == TH_HAS_PDU)) { + if (do_debug_data) + ctcm_pr_debug("ctcmpc: %s() came into th_has_pdu\n", + __FUNCTION__); + if ((fsm_getstate(grpptr->fsm) == MPCG_STATE_FLOWC) || + ((fsm_getstate(grpptr->fsm) == MPCG_STATE_READY) && + (header->th_seq_num != ch->th_seq_num + 1) && + (ch->th_seq_num != 0))) { + /* This is NOT the next segment * + * we are not the correct race winner * + * go away and let someone else win * + * BUT..this only applies if xid negot * + * is done * + */ + grpptr->out_of_sequence += 1; + __skb_push(pskb, TH_HEADER_LENGTH); + skb_queue_tail(&ch->io_queue, pskb); + if (do_debug_data) + ctcm_pr_debug("ctcmpc: %s() th_seq_num " + "expect:%08x got:%08x\n", __FUNCTION__, + ch->th_seq_num + 1, header->th_seq_num); + + return; + } + grpptr->out_of_sequence = 0; + ch->th_seq_num = header->th_seq_num; + + if (do_debug_data) + ctcm_pr_debug("ctcmpc: %s() FromVTAM_th_seq=%08x\n", + __FUNCTION__, ch->th_seq_num); + + if (unlikely(fsm_getstate(grpptr->fsm) != MPCG_STATE_READY)) + goto done; + pdu_last_seen = 0; + while ((pskb->len > 0) && !pdu_last_seen) { + curr_pdu = (struct pdu *)pskb->data; + if (do_debug_data) { + ctcm_pr_debug("ctcm: %s() pdu_header\n", + __FUNCTION__); + ctcmpc_dumpit((char *)pskb->data, + PDU_HEADER_LENGTH); + ctcm_pr_debug("ctcm: %s() pskb len: %04x \n", + __FUNCTION__, pskb->len); + } + skb_pull(pskb, PDU_HEADER_LENGTH); + + if (curr_pdu->pdu_flag & PDU_LAST) + pdu_last_seen = 1; + if (curr_pdu->pdu_flag & PDU_CNTL) + pskb->protocol = htons(ETH_P_SNAP); + else + pskb->protocol = htons(ETH_P_SNA_DIX); + + if ((pskb->len <= 0) || (pskb->len > ch->max_bufsize)) { + printk(KERN_INFO + "%s Illegal packet size %d " + "received " + "dropping\n", dev->name, + pskb->len); + privptr->stats.rx_dropped++; + privptr->stats.rx_length_errors++; + goto done; + } + skb_reset_mac_header(pskb); + new_len = curr_pdu->pdu_offset; + if (do_debug_data) + ctcm_pr_debug("ctcmpc: %s() new_len: %04x \n", + __FUNCTION__, new_len); + if ((new_len == 0) || (new_len > pskb->len)) { + /* should never happen */ + /* pskb len must be hosed...bail out */ + printk(KERN_INFO + "ctcmpc: %s(): invalid pdu" + " offset of %04x - data may be" + "lost\n", __FUNCTION__, new_len); + goto done; + } + skb = __dev_alloc_skb(new_len+4, GFP_ATOMIC); + + if (!skb) { + printk(KERN_INFO + "ctcm: %s Out of memory in " + "%s()- request-len:%04x \n", + dev->name, + __FUNCTION__, + new_len+4); + privptr->stats.rx_dropped++; + fsm_event(grpptr->fsm, + MPCG_EVENT_INOP, dev); + goto done; + } + + memcpy(skb_put(skb, new_len), + pskb->data, new_len); + + skb_reset_mac_header(skb); + skb->dev = pskb->dev; + skb->protocol = pskb->protocol; + skb->ip_summed = CHECKSUM_UNNECESSARY; + *((__u32 *) skb_push(skb, 4)) = ch->pdu_seq; + ch->pdu_seq++; + + if (do_debug_data) + ctcm_pr_debug("%s: ToDCM_pdu_seq= %08x\n", + __FUNCTION__, ch->pdu_seq); + + ctcm_pr_debug("ctcm: %s() skb:%0lx " + "skb len: %d \n", __FUNCTION__, + (unsigned long)skb, skb->len); + if (do_debug_data) { + ctcm_pr_debug("ctcmpc: %s() up to 32 bytes" + " of pdu_data sent\n", + __FUNCTION__); + ctcmpc_dump32((char *)skb->data, skb->len); + } + + skblen = skb->len; + sendrc = netif_rx(skb); + privptr->stats.rx_packets++; + privptr->stats.rx_bytes += skblen; + skb_pull(pskb, new_len); /* point to next PDU */ + } + } else { + mpcginfo = (struct mpcg_info *) + kmalloc(sizeof(struct mpcg_info), gfp_type()); + if (mpcginfo == NULL) + goto done; + + mpcginfo->ch = ch; + mpcginfo->th = header; + mpcginfo->skb = pskb; + ctcm_pr_debug("ctcmpc: %s() Not PDU - may be control pkt\n", + __FUNCTION__); + /* it's a sweep? */ + sweep = (struct th_sweep *)pskb->data; + mpcginfo->sweep = sweep; + if (header->th_ch_flag == TH_SWEEP_REQ) + mpc_rcvd_sweep_req(mpcginfo); + else if (header->th_ch_flag == TH_SWEEP_RESP) + mpc_rcvd_sweep_resp(mpcginfo); + else if (header->th_blk_flag == TH_DATA_IS_XID) { + struct xid2 *thisxid = (struct xid2 *)pskb->data; + skb_pull(pskb, XID2_LENGTH); + mpcginfo->xid = thisxid; + fsm_event(grpptr->fsm, MPCG_EVENT_XID2, mpcginfo); + } else if (header->th_blk_flag == TH_DISCONTACT) + fsm_event(grpptr->fsm, MPCG_EVENT_DISCONC, mpcginfo); + else if (header->th_seq_num != 0) { + printk(KERN_INFO "%s unexpected packet" + " expected control pkt\n", dev->name); + privptr->stats.rx_dropped++; + /* mpcginfo only used for non-data transfers */ + kfree(mpcginfo); + if (do_debug_data) + ctcmpc_dump_skb(pskb, -8); + } + } +done: + + dev_kfree_skb_any(pskb); + if (sendrc == NET_RX_DROP) { + printk(KERN_WARNING "%s %s() NETWORK BACKLOG EXCEEDED" + " - PACKET DROPPED\n", dev->name, __FUNCTION__); + fsm_event(grpptr->fsm, MPCG_EVENT_INOP, dev); + } + + if (do_debug) + ctcm_pr_debug("ctcmpc exit : %s %s(): ch=0x%p id=%s\n", + dev->name, __FUNCTION__, ch, ch->id); +} + +/** + * tasklet helper for mpc's skb unpacking. + * + * @param ch The channel to work on. + * Allow flow control back pressure to occur here. + * Throttling back channel can result in excessive + * channel inactivity and system deact of channel + */ +void ctcmpc_bh(unsigned long thischan) +{ + struct channel *ch = (struct channel *)thischan; + struct sk_buff *skb; + struct net_device *dev = ch->netdev; + struct ctcm_priv *privptr = (struct ctcm_priv *)dev->priv; + struct mpc_group *grpptr = privptr->mpcg; + + if (do_debug) + ctcm_pr_debug("%s cp:%i enter: %s() %s\n", + dev->name, smp_processor_id(), __FUNCTION__, ch->id); + /* caller has requested driver to throttle back */ + while ((fsm_getstate(grpptr->fsm) != MPCG_STATE_FLOWC) && + (skb = skb_dequeue(&ch->io_queue))) { + ctcmpc_unpack_skb(ch, skb); + if (grpptr->out_of_sequence > 20) { + /* assume data loss has occurred if */ + /* missing seq_num for extended */ + /* period of time */ + grpptr->out_of_sequence = 0; + fsm_event(grpptr->fsm, MPCG_EVENT_INOP, dev); + break; + } + if (skb == skb_peek(&ch->io_queue)) + break; + } + if (do_debug) + ctcm_pr_debug("ctcmpc exit : %s %s(): ch=0x%p id=%s\n", + dev->name, __FUNCTION__, ch, ch->id); + return; +} + +/* + * MPC Group Initializations + */ +struct mpc_group *ctcmpc_init_mpc_group(struct ctcm_priv *privptr) +{ + struct mpc_group *grpptr; + + CTCM_DBF_TEXT(MPC_SETUP, 3, __FUNCTION__); + + grpptr = kzalloc(sizeof(struct mpc_group), GFP_KERNEL); + if (grpptr == NULL) + return NULL; + + grpptr->fsm = + init_fsm("mpcg", mpcg_state_names, mpcg_event_names, + MPCG_NR_STATES, MPCG_NR_EVENTS, mpcg_fsm, + mpcg_fsm_len, GFP_KERNEL); + if (grpptr->fsm == NULL) { + kfree(grpptr); + return NULL; + } + + fsm_newstate(grpptr->fsm, MPCG_STATE_RESET); + fsm_settimer(grpptr->fsm, &grpptr->timer); + + grpptr->xid_skb = + __dev_alloc_skb(MPC_BUFSIZE_DEFAULT, GFP_ATOMIC | GFP_DMA); + if (grpptr->xid_skb == NULL) { + printk(KERN_INFO "Couldn't alloc MPCgroup xid_skb\n"); + kfree_fsm(grpptr->fsm); + kfree(grpptr); + return NULL; + } + /* base xid for all channels in group */ + grpptr->xid_skb_data = grpptr->xid_skb->data; + grpptr->xid_th = (struct th_header *)grpptr->xid_skb->data; + memcpy(skb_put(grpptr->xid_skb, TH_HEADER_LENGTH), + &thnorm, TH_HEADER_LENGTH); + + grpptr->xid = (struct xid2 *) skb_tail_pointer(grpptr->xid_skb); + memcpy(skb_put(grpptr->xid_skb, XID2_LENGTH), &init_xid, XID2_LENGTH); + grpptr->xid->xid2_adj_id = jiffies | 0xfff00000; + grpptr->xid->xid2_sender_id = jiffies; + + grpptr->xid_id = skb_tail_pointer(grpptr->xid_skb); + memcpy(skb_put(grpptr->xid_skb, 4), "VTAM", 4); + + grpptr->rcvd_xid_skb = + __dev_alloc_skb(MPC_BUFSIZE_DEFAULT, GFP_ATOMIC|GFP_DMA); + if (grpptr->rcvd_xid_skb == NULL) { + printk(KERN_INFO "Couldn't alloc MPCgroup rcvd_xid_skb\n"); + kfree_fsm(grpptr->fsm); + dev_kfree_skb(grpptr->xid_skb); + kfree(grpptr); + return NULL; + } + grpptr->rcvd_xid_data = grpptr->rcvd_xid_skb->data; + grpptr->rcvd_xid_th = (struct th_header *)grpptr->rcvd_xid_skb->data; + memcpy(skb_put(grpptr->rcvd_xid_skb, TH_HEADER_LENGTH), + &thnorm, TH_HEADER_LENGTH); + grpptr->saved_xid2 = NULL; + privptr->xid = grpptr->xid; + privptr->mpcg = grpptr; + return grpptr; +} + +/* + * The MPC Group Station FSM + */ + +/* + * MPC Group Station FSM actions + * CTCM_PROTO_MPC only + */ + +/* + * invoked when the device transitions to dev_stopped + * MPC will stop each individual channel if a single XID failure + * occurs, or will intitiate all channels be stopped if a GROUP + * level failure occurs. + */ +static void mpc_action_go_inop(fsm_instance *fi, int event, void *arg) +{ + struct net_device *dev = (struct net_device *)arg; + struct ctcm_priv *privptr; + struct mpc_group *grpptr; + int rc = 0; + struct channel *wch, *rch; + + if (dev == NULL) { + printk(KERN_INFO "%s() dev=NULL\n", __FUNCTION__); + return; + } + + ctcm_pr_debug("ctcmpc enter: %s %s()\n", dev->name, __FUNCTION__); + + privptr = (struct ctcm_priv *)dev->priv; + grpptr = privptr->mpcg; + grpptr->flow_off_called = 0; + + fsm_deltimer(&grpptr->timer); + + if (grpptr->channels_terminating) + goto done; + + grpptr->channels_terminating = 1; + + grpptr->saved_state = fsm_getstate(grpptr->fsm); + fsm_newstate(grpptr->fsm, MPCG_STATE_INOP); + if (grpptr->saved_state > MPCG_STATE_XID7INITF) + printk(KERN_NOTICE "%s:MPC GROUP INOPERATIVE\n", dev->name); + if ((grpptr->saved_state != MPCG_STATE_RESET) || + /* dealloc_channel has been called */ + ((grpptr->saved_state == MPCG_STATE_RESET) && + (grpptr->port_persist == 0))) + fsm_deltimer(&privptr->restart_timer); + + wch = privptr->channel[WRITE]; + rch = privptr->channel[READ]; + + switch (grpptr->saved_state) { + case MPCG_STATE_RESET: + case MPCG_STATE_INOP: + case MPCG_STATE_XID2INITW: + case MPCG_STATE_XID0IOWAIT: + case MPCG_STATE_XID2INITX: + case MPCG_STATE_XID7INITW: + case MPCG_STATE_XID7INITX: + case MPCG_STATE_XID0IOWAIX: + case MPCG_STATE_XID7INITI: + case MPCG_STATE_XID7INITZ: + case MPCG_STATE_XID7INITF: + break; + case MPCG_STATE_FLOWC: + case MPCG_STATE_READY: + default: + tasklet_hi_schedule(&wch->ch_disc_tasklet); + } + + grpptr->xid2_tgnum = 0; + grpptr->group_max_buflen = 0; /*min of all received */ + grpptr->outstanding_xid2 = 0; + grpptr->outstanding_xid7 = 0; + grpptr->outstanding_xid7_p2 = 0; + grpptr->saved_xid2 = NULL; + grpptr->xidnogood = 0; + grpptr->changed_side = 0; + + grpptr->rcvd_xid_skb->data = grpptr->rcvd_xid_data; + skb_reset_tail_pointer(grpptr->rcvd_xid_skb); + grpptr->rcvd_xid_skb->len = 0; + grpptr->rcvd_xid_th = (struct th_header *)grpptr->rcvd_xid_skb->data; + memcpy(skb_put(grpptr->rcvd_xid_skb, TH_HEADER_LENGTH), &thnorm, + TH_HEADER_LENGTH); + + if (grpptr->send_qllc_disc == 1) { + grpptr->send_qllc_disc = 0; + rc = mpc_send_qllc_discontact(dev); + } + + /* DO NOT issue DEV_EVENT_STOP directly out of this code */ + /* This can result in INOP of VTAM PU due to halting of */ + /* outstanding IO which causes a sense to be returned */ + /* Only about 3 senses are allowed and then IOS/VTAM will*/ + /* ebcome unreachable without manual intervention */ + if ((grpptr->port_persist == 1) || (grpptr->alloc_called)) { + grpptr->alloc_called = 0; + fsm_deltimer(&privptr->restart_timer); + fsm_addtimer(&privptr->restart_timer, + 500, + DEV_EVENT_RESTART, + dev); + fsm_newstate(grpptr->fsm, MPCG_STATE_RESET); + if (grpptr->saved_state > MPCG_STATE_XID7INITF) + printk(KERN_NOTICE "%s:MPC GROUP RECOVERY SCHEDULED\n", + dev->name); + } else { + fsm_deltimer(&privptr->restart_timer); + fsm_addtimer(&privptr->restart_timer, 500, DEV_EVENT_STOP, dev); + fsm_newstate(grpptr->fsm, MPCG_STATE_RESET); + printk(KERN_NOTICE "%s:MPC GROUP RECOVERY NOT ATTEMPTED\n", + dev->name); + } + +done: + ctcm_pr_debug("ctcmpc exit:%s %s()\n", dev->name, __FUNCTION__); + return; +} + +/** + * Handle mpc group action timeout. + * MPC Group Station FSM action + * CTCM_PROTO_MPC only + * + * @param fi An instance of an mpc_group fsm. + * @param event The event, just happened. + * @param arg Generic pointer, casted from net_device * upon call. + */ +static void mpc_action_timeout(fsm_instance *fi, int event, void *arg) +{ + struct net_device *dev = (struct net_device *)arg; + struct ctcm_priv *privptr; + struct mpc_group *grpptr; + struct channel *wch; + struct channel *rch; + + CTCM_DBF_TEXT(MPC_TRACE, 6, __FUNCTION__); + + if (dev == NULL) { + CTCM_DBF_TEXT_(MPC_ERROR, 4, "%s: dev=NULL\n", __FUNCTION__); + return; + } + + privptr = (struct ctcm_priv *)dev->priv; + grpptr = privptr->mpcg; + wch = privptr->channel[WRITE]; + rch = privptr->channel[READ]; + + switch (fsm_getstate(grpptr->fsm)) { + case MPCG_STATE_XID2INITW: + /* Unless there is outstanding IO on the */ + /* channel just return and wait for ATTN */ + /* interrupt to begin XID negotiations */ + if ((fsm_getstate(rch->fsm) == CH_XID0_PENDING) && + (fsm_getstate(wch->fsm) == CH_XID0_PENDING)) + break; + default: + fsm_event(grpptr->fsm, MPCG_EVENT_INOP, dev); + } + + CTCM_DBF_TEXT_(MPC_TRACE, 6, "%s: dev=%s exit", + __FUNCTION__, dev->name); + return; +} + +/* + * MPC Group Station FSM action + * CTCM_PROTO_MPC only + */ +void mpc_action_discontact(fsm_instance *fi, int event, void *arg) +{ + struct mpcg_info *mpcginfo = (struct mpcg_info *)arg; + struct channel *ch = mpcginfo->ch; + struct net_device *dev = ch->netdev; + struct ctcm_priv *privptr = (struct ctcm_priv *)dev->priv; + struct mpc_group *grpptr = privptr->mpcg; + + if (ch == NULL) { + printk(KERN_INFO "%s() ch=NULL\n", __FUNCTION__); + return; + } + if (ch->netdev == NULL) { + printk(KERN_INFO "%s() dev=NULL\n", __FUNCTION__); + return; + } + + ctcm_pr_debug("ctcmpc enter: %s %s()\n", dev->name, __FUNCTION__); + + grpptr->send_qllc_disc = 1; + fsm_event(grpptr->fsm, MPCG_EVENT_INOP, dev); + + ctcm_pr_debug("ctcmpc exit: %s %s()\n", dev->name, __FUNCTION__); + return; +} + +/* + * MPC Group Station - not part of FSM + * CTCM_PROTO_MPC only + * called from add_channel in ctcm_main.c + */ +void mpc_action_send_discontact(unsigned long thischan) +{ + struct channel *ch; + struct net_device *dev; + struct ctcm_priv *privptr; + struct mpc_group *grpptr; + int rc = 0; + unsigned long saveflags; + + ch = (struct channel *)thischan; + dev = ch->netdev; + privptr = (struct ctcm_priv *)dev->priv; + grpptr = privptr->mpcg; + + ctcm_pr_info("ctcmpc: %s cp:%i enter: %s() GrpState:%s ChState:%s\n", + dev->name, + smp_processor_id(), + __FUNCTION__, + fsm_getstate_str(grpptr->fsm), + fsm_getstate_str(ch->fsm)); + saveflags = 0; /* avoids compiler warning with + spin_unlock_irqrestore */ + + spin_lock_irqsave(get_ccwdev_lock(ch->cdev), saveflags); + rc = ccw_device_start(ch->cdev, &ch->ccw[15], + (unsigned long)ch, 0xff, 0); + spin_unlock_irqrestore(get_ccwdev_lock(ch->cdev), saveflags); + + if (rc != 0) { + ctcm_pr_info("ctcmpc: %s() ch:%s IO failed \n", + __FUNCTION__, + ch->id); + ctcm_ccw_check_rc(ch, rc, "send discontact"); + /* Not checking return code value here */ + /* Making best effort to notify partner*/ + /* that MPC Group is going down */ + } + + ctcm_pr_debug("ctcmpc exit: %s %s()\n", dev->name, __FUNCTION__); + return; +} + + +/* + * helper function of mpc FSM + * CTCM_PROTO_MPC only + * mpc_action_rcvd_xid7 +*/ +static int mpc_validate_xid(struct mpcg_info *mpcginfo) +{ + struct channel *ch = mpcginfo->ch; + struct net_device *dev = ch->netdev; + struct ctcm_priv *privptr = (struct ctcm_priv *)dev->priv; + struct mpc_group *grpptr = privptr->mpcg; + struct xid2 *xid = mpcginfo->xid; + int failed = 0; + int rc = 0; + __u64 our_id, their_id = 0; + int len; + + len = TH_HEADER_LENGTH + PDU_HEADER_LENGTH; + + ctcm_pr_debug("ctcmpc enter: %s()\n", __FUNCTION__); + + if (mpcginfo->xid == NULL) { + printk(KERN_INFO "%s() xid=NULL\n", __FUNCTION__); + rc = 1; + goto done; + } + + ctcm_pr_debug("ctcmpc : %s xid received()\n", __FUNCTION__); + ctcmpc_dumpit((char *)mpcginfo->xid, XID2_LENGTH); + + /*the received direction should be the opposite of ours */ + if (((CHANNEL_DIRECTION(ch->flags) == READ) ? XID2_WRITE_SIDE : + XID2_READ_SIDE) != xid->xid2_dlc_type) { + failed = 1; + printk(KERN_INFO "ctcmpc:%s() XID REJECTED - READ-WRITE CH " + "Pairing Invalid \n", __FUNCTION__); + } + + if (xid->xid2_dlc_type == XID2_READ_SIDE) { + ctcm_pr_debug("ctcmpc: %s(): grpmaxbuf:%d xid2buflen:%d\n", + __FUNCTION__, grpptr->group_max_buflen, + xid->xid2_buf_len); + + if (grpptr->group_max_buflen == 0 || + grpptr->group_max_buflen > xid->xid2_buf_len - len) + grpptr->group_max_buflen = xid->xid2_buf_len - len; + } + + + if (grpptr->saved_xid2 == NULL) { + grpptr->saved_xid2 = + (struct xid2 *)skb_tail_pointer(grpptr->rcvd_xid_skb); + + memcpy(skb_put(grpptr->rcvd_xid_skb, + XID2_LENGTH), xid, XID2_LENGTH); + grpptr->rcvd_xid_skb->data = grpptr->rcvd_xid_data; + + skb_reset_tail_pointer(grpptr->rcvd_xid_skb); + grpptr->rcvd_xid_skb->len = 0; + + /* convert two 32 bit numbers into 1 64 bit for id compare */ + our_id = (__u64)privptr->xid->xid2_adj_id; + our_id = our_id << 32; + our_id = our_id + privptr->xid->xid2_sender_id; + their_id = (__u64)xid->xid2_adj_id; + their_id = their_id << 32; + their_id = their_id + xid->xid2_sender_id; + /* lower id assume the xside role */ + if (our_id < their_id) { + grpptr->roll = XSIDE; + ctcm_pr_debug("ctcmpc :%s() WE HAVE LOW ID-" + "TAKE XSIDE\n", __FUNCTION__); + } else { + grpptr->roll = YSIDE; + ctcm_pr_debug("ctcmpc :%s() WE HAVE HIGH ID-" + "TAKE YSIDE\n", __FUNCTION__); + } + + } else { + if (xid->xid2_flag4 != grpptr->saved_xid2->xid2_flag4) { + failed = 1; + printk(KERN_INFO "%s XID REJECTED - XID Flag Byte4\n", + __FUNCTION__); + } + if (xid->xid2_flag2 == 0x40) { + failed = 1; + printk(KERN_INFO "%s XID REJECTED - XID NOGOOD\n", + __FUNCTION__); + } + if (xid->xid2_adj_id != grpptr->saved_xid2->xid2_adj_id) { + failed = 1; + printk(KERN_INFO "%s XID REJECTED - " + "Adjacent Station ID Mismatch\n", + __FUNCTION__); + } + if (xid->xid2_sender_id != grpptr->saved_xid2->xid2_sender_id) { + failed = 1; + printk(KERN_INFO "%s XID REJECTED - " + "Sender Address Mismatch\n", __FUNCTION__); + + } + } + + if (failed) { + ctcm_pr_info("ctcmpc : %s() failed\n", __FUNCTION__); + privptr->xid->xid2_flag2 = 0x40; + grpptr->saved_xid2->xid2_flag2 = 0x40; + rc = 1; + } + +done: + + ctcm_pr_debug("ctcmpc exit: %s()\n", __FUNCTION__); + return rc; +} + +/* + * MPC Group Station FSM action + * CTCM_PROTO_MPC only + */ +static void mpc_action_side_xid(fsm_instance *fsm, void *arg, int side) +{ + struct channel *ch = (struct channel *)arg; + struct ctcm_priv *privptr; + struct mpc_group *grpptr = NULL; + struct net_device *dev = NULL; + int rc = 0; + int gotlock = 0; + unsigned long saveflags = 0; /* avoids compiler warning with + spin_unlock_irqrestore */ + + if (ch == NULL) { + printk(KERN_INFO "%s ch=NULL\n", __FUNCTION__); + goto done; + } + + if (do_debug) + ctcm_pr_debug("ctcmpc enter: %s(): cp=%i ch=0x%p id=%s\n", + __FUNCTION__, smp_processor_id(), ch, ch->id); + + dev = ch->netdev; + if (dev == NULL) { + printk(KERN_INFO "%s dev=NULL\n", __FUNCTION__); + goto done; + } + + privptr = (struct ctcm_priv *)dev->priv; + if (privptr == NULL) { + printk(KERN_INFO "%s privptr=NULL\n", __FUNCTION__); + goto done; + } + + grpptr = privptr->mpcg; + if (grpptr == NULL) { + printk(KERN_INFO "%s grpptr=NULL\n", __FUNCTION__); + goto done; + } + + if (ctcm_checkalloc_buffer(ch)) + goto done; + + /* skb data-buffer referencing: */ + + ch->trans_skb->data = ch->trans_skb_data; + skb_reset_tail_pointer(ch->trans_skb); + ch->trans_skb->len = 0; + /* result of the previous 3 statements is NOT always + * already set after ctcm_checkalloc_buffer + * because of possible reuse of the trans_skb + */ + memset(ch->trans_skb->data, 0, 16); + ch->rcvd_xid_th = (struct th_header *)ch->trans_skb_data; + /* check is main purpose here: */ + skb_put(ch->trans_skb, TH_HEADER_LENGTH); + ch->rcvd_xid = (struct xid2 *)skb_tail_pointer(ch->trans_skb); + /* check is main purpose here: */ + skb_put(ch->trans_skb, XID2_LENGTH); + ch->rcvd_xid_id = skb_tail_pointer(ch->trans_skb); + /* cleanup back to startpoint */ + ch->trans_skb->data = ch->trans_skb_data; + skb_reset_tail_pointer(ch->trans_skb); + ch->trans_skb->len = 0; + + /* non-checking rewrite of above skb data-buffer referencing: */ + /* + memset(ch->trans_skb->data, 0, 16); + ch->rcvd_xid_th = (struct th_header *)ch->trans_skb_data; + ch->rcvd_xid = (struct xid2 *)(ch->trans_skb_data + TH_HEADER_LENGTH); + ch->rcvd_xid_id = ch->trans_skb_data + TH_HEADER_LENGTH + XID2_LENGTH; + */ + + ch->ccw[8].flags = CCW_FLAG_SLI | CCW_FLAG_CC; + ch->ccw[8].count = 0; + ch->ccw[8].cda = 0x00; + + if (side == XSIDE) { + /* mpc_action_xside_xid */ + if (ch->xid_th == NULL) { + printk(KERN_INFO "%s ch->xid_th=NULL\n", __FUNCTION__); + goto done; + } + ch->ccw[9].cmd_code = CCW_CMD_WRITE; + ch->ccw[9].flags = CCW_FLAG_SLI | CCW_FLAG_CC; + ch->ccw[9].count = TH_HEADER_LENGTH; + ch->ccw[9].cda = virt_to_phys(ch->xid_th); + + if (ch->xid == NULL) { + printk(KERN_INFO "%s ch->xid=NULL\n", __FUNCTION__); + goto done; + } + + ch->ccw[10].cmd_code = CCW_CMD_WRITE; + ch->ccw[10].flags = CCW_FLAG_SLI | CCW_FLAG_CC; + ch->ccw[10].count = XID2_LENGTH; + ch->ccw[10].cda = virt_to_phys(ch->xid); + + ch->ccw[11].cmd_code = CCW_CMD_READ; + ch->ccw[11].flags = CCW_FLAG_SLI | CCW_FLAG_CC; + ch->ccw[11].count = TH_HEADER_LENGTH; + ch->ccw[11].cda = virt_to_phys(ch->rcvd_xid_th); + + ch->ccw[12].cmd_code = CCW_CMD_READ; + ch->ccw[12].flags = CCW_FLAG_SLI | CCW_FLAG_CC; + ch->ccw[12].count = XID2_LENGTH; + ch->ccw[12].cda = virt_to_phys(ch->rcvd_xid); + + ch->ccw[13].cmd_code = CCW_CMD_READ; + ch->ccw[13].cda = virt_to_phys(ch->rcvd_xid_id); + + } else { /* side == YSIDE : mpc_action_yside_xid */ + ch->ccw[9].cmd_code = CCW_CMD_READ; + ch->ccw[9].flags = CCW_FLAG_SLI | CCW_FLAG_CC; + ch->ccw[9].count = TH_HEADER_LENGTH; + ch->ccw[9].cda = virt_to_phys(ch->rcvd_xid_th); + + ch->ccw[10].cmd_code = CCW_CMD_READ; + ch->ccw[10].flags = CCW_FLAG_SLI | CCW_FLAG_CC; + ch->ccw[10].count = XID2_LENGTH; + ch->ccw[10].cda = virt_to_phys(ch->rcvd_xid); + + if (ch->xid_th == NULL) { + printk(KERN_INFO "%s ch->xid_th=NULL\n", __FUNCTION__); + goto done; + } + ch->ccw[11].cmd_code = CCW_CMD_WRITE; + ch->ccw[11].flags = CCW_FLAG_SLI | CCW_FLAG_CC; + ch->ccw[11].count = TH_HEADER_LENGTH; + ch->ccw[11].cda = virt_to_phys(ch->xid_th); + + if (ch->xid == NULL) { + printk(KERN_INFO "%s ch->xid=NULL\n", __FUNCTION__); + goto done; + } + ch->ccw[12].cmd_code = CCW_CMD_WRITE; + ch->ccw[12].flags = CCW_FLAG_SLI | CCW_FLAG_CC; + ch->ccw[12].count = XID2_LENGTH; + ch->ccw[12].cda = virt_to_phys(ch->xid); + + if (ch->xid_id == NULL) { + printk(KERN_INFO "%s ch->xid_id=NULL\n", __FUNCTION__); + goto done; + } + ch->ccw[13].cmd_code = CCW_CMD_WRITE; + ch->ccw[13].cda = virt_to_phys(ch->xid_id); + + } + ch->ccw[13].flags = CCW_FLAG_SLI | CCW_FLAG_CC; + ch->ccw[13].count = 4; + + ch->ccw[14].cmd_code = CCW_CMD_NOOP; + ch->ccw[14].flags = CCW_FLAG_SLI; + ch->ccw[14].count = 0; + ch->ccw[14].cda = 0; + + if (do_debug_ccw) + ctcmpc_dumpit((char *)&ch->ccw[8], sizeof(struct ccw1) * 7); + + ctcmpc_dumpit((char *)ch->xid_th, TH_HEADER_LENGTH); + ctcmpc_dumpit((char *)ch->xid, XID2_LENGTH); + ctcmpc_dumpit((char *)ch->xid_id, 4); + if (!in_irq()) { + /* Such conditional locking is a known problem for + * sparse because its static undeterministic. + * Warnings should be ignored here. */ + spin_lock_irqsave(get_ccwdev_lock(ch->cdev), saveflags); + gotlock = 1; + } + + fsm_addtimer(&ch->timer, 5000 , CTC_EVENT_TIMER, ch); + rc = ccw_device_start(ch->cdev, &ch->ccw[8], + (unsigned long)ch, 0xff, 0); + + if (gotlock) /* see remark above about conditional locking */ + spin_unlock_irqrestore(get_ccwdev_lock(ch->cdev), saveflags); + + if (rc != 0) { + ctcm_pr_info("ctcmpc: %s() ch:%s IO failed \n", + __FUNCTION__, ch->id); + ctcm_ccw_check_rc(ch, rc, + (side == XSIDE) ? "x-side XID" : "y-side XID"); + } + +done: + if (do_debug) + ctcm_pr_debug("ctcmpc exit : %s(): ch=0x%p id=%s\n", + __FUNCTION__, ch, ch->id); + return; + +} + +/* + * MPC Group Station FSM action + * CTCM_PROTO_MPC only + */ +static void mpc_action_xside_xid(fsm_instance *fsm, int event, void *arg) +{ + mpc_action_side_xid(fsm, arg, XSIDE); +} + +/* + * MPC Group Station FSM action + * CTCM_PROTO_MPC only + */ +static void mpc_action_yside_xid(fsm_instance *fsm, int event, void *arg) +{ + mpc_action_side_xid(fsm, arg, YSIDE); +} + +/* + * MPC Group Station FSM action + * CTCM_PROTO_MPC only + */ +static void mpc_action_doxid0(fsm_instance *fsm, int event, void *arg) +{ + struct channel *ch; + struct ctcm_priv *privptr; + struct mpc_group *grpptr = NULL; + struct net_device *dev = NULL; + + ch = (struct channel *)arg; + + if (do_debug) + ctcm_pr_debug("ctcmpc enter: %s(): cp=%i ch=0x%p id=%s\n", + __FUNCTION__, smp_processor_id(), ch, ch->id); + + if (ch == NULL) { + printk(KERN_WARNING "%s ch=NULL\n", __FUNCTION__); + goto done; + } + + dev = ch->netdev; + if (dev == NULL) { + printk(KERN_WARNING "%s dev=NULL\n", __FUNCTION__); + goto done; + } + + privptr = (struct ctcm_priv *)dev->priv; + if (privptr == NULL) { + printk(KERN_WARNING "%s privptr=NULL\n", __FUNCTION__); + goto done; + } + + grpptr = privptr->mpcg; + if (grpptr == NULL) { + printk(KERN_WARNING "%s grpptr=NULL\n", __FUNCTION__); + goto done; + } + + if (ch->xid == NULL) { + printk(KERN_WARNING "%s ch-xid=NULL\n", __FUNCTION__); + goto done; + } + + fsm_newstate(ch->fsm, CH_XID0_INPROGRESS); + + ch->xid->xid2_option = XID2_0; + + switch (fsm_getstate(grpptr->fsm)) { + case MPCG_STATE_XID2INITW: + case MPCG_STATE_XID2INITX: + ch->ccw[8].cmd_code = CCW_CMD_SENSE_CMD; + break; + case MPCG_STATE_XID0IOWAIT: + case MPCG_STATE_XID0IOWAIX: + ch->ccw[8].cmd_code = CCW_CMD_WRITE_CTL; + break; + } + + fsm_event(grpptr->fsm, MPCG_EVENT_DOIO, ch); + +done: + if (do_debug) + ctcm_pr_debug("ctcmpc exit : %s(): ch=0x%p id=%s\n", + __FUNCTION__, ch, ch->id); + return; + +} + +/* + * MPC Group Station FSM action + * CTCM_PROTO_MPC only +*/ +static void mpc_action_doxid7(fsm_instance *fsm, int event, void *arg) +{ + struct net_device *dev = (struct net_device *)arg; + struct ctcm_priv *privptr = NULL; + struct mpc_group *grpptr = NULL; + int direction; + int rc = 0; + int send = 0; + + ctcm_pr_debug("ctcmpc enter: %s() \n", __FUNCTION__); + + if (dev == NULL) { + printk(KERN_INFO "%s dev=NULL \n", __FUNCTION__); + rc = 1; + goto done; + } + + privptr = (struct ctcm_priv *)dev->priv; + if (privptr == NULL) { + printk(KERN_INFO "%s privptr=NULL \n", __FUNCTION__); + rc = 1; + goto done; + } + + grpptr = privptr->mpcg; + if (grpptr == NULL) { + printk(KERN_INFO "%s grpptr=NULL \n", __FUNCTION__); + rc = 1; + goto done; + } + + for (direction = READ; direction <= WRITE; direction++) { + struct channel *ch = privptr->channel[direction]; + struct xid2 *thisxid = ch->xid; + ch->xid_skb->data = ch->xid_skb_data; + skb_reset_tail_pointer(ch->xid_skb); + ch->xid_skb->len = 0; + thisxid->xid2_option = XID2_7; + send = 0; + + /* xid7 phase 1 */ + if (grpptr->outstanding_xid7_p2 > 0) { + if (grpptr->roll == YSIDE) { + if (fsm_getstate(ch->fsm) == CH_XID7_PENDING1) { + fsm_newstate(ch->fsm, CH_XID7_PENDING2); + ch->ccw[8].cmd_code = CCW_CMD_SENSE_CMD; + memcpy(skb_put(ch->xid_skb, + TH_HEADER_LENGTH), + &thdummy, TH_HEADER_LENGTH); + send = 1; + } + } else if (fsm_getstate(ch->fsm) < CH_XID7_PENDING2) { + fsm_newstate(ch->fsm, CH_XID7_PENDING2); + ch->ccw[8].cmd_code = CCW_CMD_WRITE_CTL; + memcpy(skb_put(ch->xid_skb, + TH_HEADER_LENGTH), + &thnorm, TH_HEADER_LENGTH); + send = 1; + } + } else { + /* xid7 phase 2 */ + if (grpptr->roll == YSIDE) { + if (fsm_getstate(ch->fsm) < CH_XID7_PENDING4) { + fsm_newstate(ch->fsm, CH_XID7_PENDING4); + memcpy(skb_put(ch->xid_skb, + TH_HEADER_LENGTH), + &thnorm, TH_HEADER_LENGTH); + ch->ccw[8].cmd_code = CCW_CMD_WRITE_CTL; + send = 1; + } + } else if (fsm_getstate(ch->fsm) == CH_XID7_PENDING3) { + fsm_newstate(ch->fsm, CH_XID7_PENDING4); + ch->ccw[8].cmd_code = CCW_CMD_SENSE_CMD; + memcpy(skb_put(ch->xid_skb, TH_HEADER_LENGTH), + &thdummy, TH_HEADER_LENGTH); + send = 1; + } + } + + if (send) + fsm_event(grpptr->fsm, MPCG_EVENT_DOIO, ch); + } + +done: + + if (rc != 0) + fsm_event(grpptr->fsm, MPCG_EVENT_INOP, dev); + + return; +} + +/* + * MPC Group Station FSM action + * CTCM_PROTO_MPC only + */ +static void mpc_action_rcvd_xid0(fsm_instance *fsm, int event, void *arg) +{ + + struct mpcg_info *mpcginfo = (struct mpcg_info *)arg; + struct channel *ch = mpcginfo->ch; + struct net_device *dev = ch->netdev; + struct ctcm_priv *privptr; + struct mpc_group *grpptr; + + if (do_debug) + ctcm_pr_debug("ctcmpc enter: %s(): cp=%i ch=0x%p id=%s\n", + __FUNCTION__, smp_processor_id(), ch, ch->id); + + privptr = (struct ctcm_priv *)dev->priv; + grpptr = privptr->mpcg; + + ctcm_pr_debug("ctcmpc in:%s() %s xid2:%i xid7:%i xidt_p2:%i \n", + __FUNCTION__, ch->id, + grpptr->outstanding_xid2, + grpptr->outstanding_xid7, + grpptr->outstanding_xid7_p2); + + if (fsm_getstate(ch->fsm) < CH_XID7_PENDING) + fsm_newstate(ch->fsm, CH_XID7_PENDING); + + grpptr->outstanding_xid2--; + grpptr->outstanding_xid7++; + grpptr->outstanding_xid7_p2++; + + /* must change state before validating xid to */ + /* properly handle interim interrupts received*/ + switch (fsm_getstate(grpptr->fsm)) { + case MPCG_STATE_XID2INITW: + fsm_newstate(grpptr->fsm, MPCG_STATE_XID2INITX); + mpc_validate_xid(mpcginfo); + break; + case MPCG_STATE_XID0IOWAIT: + fsm_newstate(grpptr->fsm, MPCG_STATE_XID0IOWAIX); + mpc_validate_xid(mpcginfo); + break; + case MPCG_STATE_XID2INITX: + if (grpptr->outstanding_xid2 == 0) { + fsm_newstate(grpptr->fsm, MPCG_STATE_XID7INITW); + mpc_validate_xid(mpcginfo); + fsm_event(grpptr->fsm, MPCG_EVENT_XID2DONE, dev); + } + break; + case MPCG_STATE_XID0IOWAIX: + if (grpptr->outstanding_xid2 == 0) { + fsm_newstate(grpptr->fsm, MPCG_STATE_XID7INITI); + mpc_validate_xid(mpcginfo); + fsm_event(grpptr->fsm, MPCG_EVENT_XID2DONE, dev); + } + break; + } + kfree(mpcginfo); + + if (do_debug) { + ctcm_pr_debug("ctcmpc:%s() %s xid2:%i xid7:%i xidt_p2:%i \n", + __FUNCTION__, ch->id, + grpptr->outstanding_xid2, + grpptr->outstanding_xid7, + grpptr->outstanding_xid7_p2); + ctcm_pr_debug("ctcmpc:%s() %s grpstate: %s chanstate: %s \n", + __FUNCTION__, ch->id, + fsm_getstate_str(grpptr->fsm), + fsm_getstate_str(ch->fsm)); + } + return; + +} + + +/* + * MPC Group Station FSM action + * CTCM_PROTO_MPC only + */ +static void mpc_action_rcvd_xid7(fsm_instance *fsm, int event, void *arg) +{ + struct mpcg_info *mpcginfo = (struct mpcg_info *)arg; + struct channel *ch = mpcginfo->ch; + struct net_device *dev = ch->netdev; + struct ctcm_priv *privptr = (struct ctcm_priv *)dev->priv; + struct mpc_group *grpptr = privptr->mpcg; + + if (do_debug) { + ctcm_pr_debug("ctcmpc enter: %s(): cp=%i ch=0x%p id=%s\n", + __FUNCTION__, smp_processor_id(), ch, ch->id); + + ctcm_pr_debug("ctcmpc: outstanding_xid7: %i, " + " outstanding_xid7_p2: %i\n", + grpptr->outstanding_xid7, + grpptr->outstanding_xid7_p2); + } + + grpptr->outstanding_xid7--; + ch->xid_skb->data = ch->xid_skb_data; + skb_reset_tail_pointer(ch->xid_skb); + ch->xid_skb->len = 0; + + switch (fsm_getstate(grpptr->fsm)) { + case MPCG_STATE_XID7INITI: + fsm_newstate(grpptr->fsm, MPCG_STATE_XID7INITZ); + mpc_validate_xid(mpcginfo); + break; + case MPCG_STATE_XID7INITW: + fsm_newstate(grpptr->fsm, MPCG_STATE_XID7INITX); + mpc_validate_xid(mpcginfo); + break; + case MPCG_STATE_XID7INITZ: + case MPCG_STATE_XID7INITX: + if (grpptr->outstanding_xid7 == 0) { + if (grpptr->outstanding_xid7_p2 > 0) { + grpptr->outstanding_xid7 = + grpptr->outstanding_xid7_p2; + grpptr->outstanding_xid7_p2 = 0; + } else + fsm_newstate(grpptr->fsm, MPCG_STATE_XID7INITF); + + mpc_validate_xid(mpcginfo); + fsm_event(grpptr->fsm, MPCG_EVENT_XID7DONE, dev); + break; + } + mpc_validate_xid(mpcginfo); + break; + } + + kfree(mpcginfo); + + if (do_debug) + ctcm_pr_debug("ctcmpc exit: %s(): cp=%i ch=0x%p id=%s\n", + __FUNCTION__, smp_processor_id(), ch, ch->id); + return; + +} + +/* + * mpc_action helper of an MPC Group Station FSM action + * CTCM_PROTO_MPC only + */ +static int mpc_send_qllc_discontact(struct net_device *dev) +{ + int rc = 0; + __u32 new_len = 0; + struct sk_buff *skb; + struct qllc *qllcptr; + struct ctcm_priv *privptr; + struct mpc_group *grpptr; + + ctcm_pr_debug("ctcmpc enter: %s()\n", __FUNCTION__); + + if (dev == NULL) { + printk(KERN_INFO "%s() dev=NULL\n", __FUNCTION__); + rc = 1; + goto done; + } + + privptr = (struct ctcm_priv *)dev->priv; + if (privptr == NULL) { + printk(KERN_INFO "%s() privptr=NULL\n", __FUNCTION__); + rc = 1; + goto done; + } + + grpptr = privptr->mpcg; + if (grpptr == NULL) { + printk(KERN_INFO "%s() grpptr=NULL\n", __FUNCTION__); + rc = 1; + goto done; + } + ctcm_pr_info("ctcmpc: %s() GROUP STATE: %s\n", __FUNCTION__, + mpcg_state_names[grpptr->saved_state]); + + switch (grpptr->saved_state) { + /* + * establish conn callback function is + * preferred method to report failure + */ + case MPCG_STATE_XID0IOWAIT: + case MPCG_STATE_XID0IOWAIX: + case MPCG_STATE_XID7INITI: + case MPCG_STATE_XID7INITZ: + case MPCG_STATE_XID2INITW: + case MPCG_STATE_XID2INITX: + case MPCG_STATE_XID7INITW: + case MPCG_STATE_XID7INITX: + if (grpptr->estconnfunc) { + grpptr->estconnfunc(grpptr->port_num, -1, 0); + grpptr->estconnfunc = NULL; + break; + } + case MPCG_STATE_FLOWC: + case MPCG_STATE_READY: + grpptr->send_qllc_disc = 2; + new_len = sizeof(struct qllc); + qllcptr = kzalloc(new_len, gfp_type() | GFP_DMA); + if (qllcptr == NULL) { + printk(KERN_INFO + "ctcmpc: Out of memory in %s()\n", + dev->name); + rc = 1; + goto done; + } + + qllcptr->qllc_address = 0xcc; + qllcptr->qllc_commands = 0x03; + + skb = __dev_alloc_skb(new_len, GFP_ATOMIC); + + if (skb == NULL) { + printk(KERN_INFO "%s Out of memory in mpc_send_qllc\n", + dev->name); + privptr->stats.rx_dropped++; + rc = 1; + kfree(qllcptr); + goto done; + } + + memcpy(skb_put(skb, new_len), qllcptr, new_len); + kfree(qllcptr); + + if (skb_headroom(skb) < 4) { + printk(KERN_INFO "ctcmpc: %s() Unable to" + " build discontact for %s\n", + __FUNCTION__, dev->name); + rc = 1; + dev_kfree_skb_any(skb); + goto done; + } + + *((__u32 *)skb_push(skb, 4)) = privptr->channel[READ]->pdu_seq; + privptr->channel[READ]->pdu_seq++; + if (do_debug_data) + ctcm_pr_debug("ctcmpc: %s ToDCM_pdu_seq= %08x\n", + __FUNCTION__, privptr->channel[READ]->pdu_seq); + + /* receipt of CC03 resets anticipated sequence number on + receiving side */ + privptr->channel[READ]->pdu_seq = 0x00; + skb_reset_mac_header(skb); + skb->dev = dev; + skb->protocol = htons(ETH_P_SNAP); + skb->ip_summed = CHECKSUM_UNNECESSARY; + + ctcmpc_dumpit((char *)skb->data, (sizeof(struct qllc) + 4)); + + netif_rx(skb); + break; + default: + break; + + } + +done: + ctcm_pr_debug("ctcmpc exit: %s()\n", __FUNCTION__); + return rc; +} +/* --- This is the END my friend --- */ + Index: linux-2.6-uschi/drivers/s390/net/ctcm_mpc.h =================================================================== --- /dev/null +++ linux-2.6-uschi/drivers/s390/net/ctcm_mpc.h @@ -0,0 +1,239 @@ +/* + * drivers/s390/net/ctcm_mpc.h + * + * Copyright IBM Corp. 2007 + * Authors: Peter Tiedemann (ptiedem@de.ibm.com) + * + * MPC additions: + * Belinda Thompson (belindat@us.ibm.com) + * Andy Richter (richtera@us.ibm.com) + */ + +#ifndef _CTC_MPC_H_ +#define _CTC_MPC_H_ + +#include <linux/skbuff.h> +#include "fsm.h" + +/* + * MPC external interface + * Note that ctc_mpc_xyz are called with a lock on ................ + */ + +/* port_number is the mpc device 0, 1, 2 etc mpc2 is port_number 2 */ + +/* passive open Just wait for XID2 exchange */ +extern int ctc_mpc_alloc_channel(int port, + void (*callback)(int port_num, int max_write_size)); +/* active open Alloc then send XID2 */ +extern void ctc_mpc_establish_connectivity(int port, + void (*callback)(int port_num, int rc, int max_write_size)); + +extern void ctc_mpc_dealloc_ch(int port); +extern void ctc_mpc_flow_control(int port, int flowc); + +/* + * other MPC Group prototypes and structures + */ + +#define ETH_P_SNA_DIX 0x80D5 + +/* + * Declaration of an XID2 + * + */ +#define ALLZEROS 0x0000000000000000 + +#define XID_FM2 0x20 +#define XID2_0 0x00 +#define XID2_7 0x07 +#define XID2_WRITE_SIDE 0x04 +#define XID2_READ_SIDE 0x05 + +struct xid2 { + __u8 xid2_type_id; + __u8 xid2_len; + __u32 xid2_adj_id; + __u8 xid2_rlen; + __u8 xid2_resv1; + __u8 xid2_flag1; + __u8 xid2_fmtt; + __u8 xid2_flag4; + __u16 xid2_resv2; + __u8 xid2_tgnum; + __u32 xid2_sender_id; + __u8 xid2_flag2; + __u8 xid2_option; + char xid2_resv3[8]; + __u16 xid2_resv4; + __u8 xid2_dlc_type; + __u16 xid2_resv5; + __u8 xid2_mpc_flag; + __u8 xid2_resv6; + __u16 xid2_buf_len; + char xid2_buffer[255 - (13 * sizeof(__u8) + + 2 * sizeof(__u32) + + 4 * sizeof(__u16) + + 8 * sizeof(char))]; +} __attribute__ ((packed)); + +#define XID2_LENGTH (sizeof(struct xid2)) + +struct th_header { + __u8 th_seg; + __u8 th_ch_flag; +#define TH_HAS_PDU 0xf0 +#define TH_IS_XID 0x01 +#define TH_SWEEP_REQ 0xfe +#define TH_SWEEP_RESP 0xff + __u8 th_blk_flag; +#define TH_DATA_IS_XID 0x80 +#define TH_RETRY 0x40 +#define TH_DISCONTACT 0xc0 +#define TH_SEG_BLK 0x20 +#define TH_LAST_SEG 0x10 +#define TH_PDU_PART 0x08 + __u8 th_is_xid; /* is 0x01 if this is XID */ + __u32 th_seq_num; +} __attribute__ ((packed)); + +struct th_addon { + __u32 th_last_seq; + __u32 th_resvd; +} __attribute__ ((packed)); + +struct th_sweep { + struct th_header th; + struct th_addon sw; +} __attribute__ ((packed)); + +#define TH_HEADER_LENGTH (sizeof(struct th_header)) +#define TH_SWEEP_LENGTH (sizeof(struct th_sweep)) + +#define PDU_LAST 0x80 +#define PDU_CNTL 0x40 +#define PDU_FIRST 0x20 + +struct pdu { + __u32 pdu_offset; + __u8 pdu_flag; + __u8 pdu_proto; /* 0x01 is APPN SNA */ + __u16 pdu_seq; +} __attribute__ ((packed)); + +#define PDU_HEADER_LENGTH (sizeof(struct pdu)) + +struct qllc { + __u8 qllc_address; +#define QLLC_REQ 0xFF +#define QLLC_RESP 0x00 + __u8 qllc_commands; +#define QLLC_DISCONNECT 0x53 +#define QLLC_UNSEQACK 0x73 +#define QLLC_SETMODE 0x93 +#define QLLC_EXCHID 0xBF +} __attribute__ ((packed)); + + +/* + * Definition of one MPC group + */ + +#define MAX_MPCGCHAN 10 +#define MPC_XID_TIMEOUT_VALUE 10000 +#define MPC_CHANNEL_ADD 0 +#define MPC_CHANNEL_REMOVE 1 +#define MPC_CHANNEL_ATTN 2 +#define XSIDE 1 +#define YSIDE 0 + +struct mpcg_info { + struct sk_buff *skb; + struct channel *ch; + struct xid2 *xid; + struct th_sweep *sweep; + struct th_header *th; +}; + +struct mpc_group { + struct tasklet_struct mpc_tasklet; + struct tasklet_struct mpc_tasklet2; + int changed_side; + int saved_state; + int channels_terminating; + int out_of_sequence; + int flow_off_called; + int port_num; + int port_persist; + int alloc_called; + __u32 xid2_adj_id; + __u8 xid2_tgnum; + __u32 xid2_sender_id; + int num_channel_paths; + int active_channels[2]; + __u16 group_max_buflen; + int outstanding_xid2; + int outstanding_xid7; + int outstanding_xid7_p2; + int sweep_req_pend_num; + int sweep_rsp_pend_num; + struct sk_buff *xid_skb; + char *xid_skb_data; + struct th_header *xid_th; + struct xid2 *xid; + char *xid_id; + struct th_header *rcvd_xid_th; + struct sk_buff *rcvd_xid_skb; + char *rcvd_xid_data; + __u8 in_sweep; + __u8 roll; + struct xid2 *saved_xid2; + void (*allochanfunc)(int, int); + int allocchan_callback_retries; + void (*estconnfunc)(int, int, int); + int estconn_callback_retries; + int estconn_called; + int xidnogood; + int send_qllc_disc; + fsm_timer timer; + fsm_instance *fsm; /* group xid fsm */ +}; + +#ifdef DEBUGDATA +void ctcmpc_dumpit(char *buf, int len); +#else +static inline void ctcmpc_dumpit(char *buf, int len) +{ +} +#endif + +#ifdef DEBUGDATA +/* + * Dump header and first 16 bytes of an sk_buff for debugging purposes. + * + * @param skb The struct sk_buff to dump. + * @param offset Offset relative to skb-data, where to start the dump. + */ +void ctcmpc_dump_skb(struct sk_buff *skb, int offset); +#else +static inline void ctcmpc_dump_skb(struct sk_buff *skb, int offset) +{} +#endif + +static inline void ctcmpc_dump32(char *buf, int len) +{ + if (len < 32) + ctcmpc_dumpit(buf, len); + else + ctcmpc_dumpit(buf, 32); +} + +int ctcmpc_open(struct net_device *); +void ctcm_ccw_check_rc(struct channel *, int, char *); +void mpc_group_ready(unsigned long adev); +int mpc_channel_action(struct channel *ch, int direction, int action); +void mpc_action_send_discontact(unsigned long thischan); +void mpc_action_discontact(fsm_instance *fi, int event, void *arg); +void ctcmpc_bh(unsigned long thischan); +#endif +/* --- This is the END my friend --- */ Index: linux-2.6-uschi/drivers/s390/net/ctcm_sysfs.c =================================================================== --- /dev/null +++ linux-2.6-uschi/drivers/s390/net/ctcm_sysfs.c @@ -0,0 +1,210 @@ +/* + * drivers/s390/net/ctcm_sysfs.c + * + * Copyright IBM Corp. 2007, 2007 + * Authors: Peter Tiedemann (ptiedem@de.ibm.com) + * + */ + +#undef DEBUG +#undef DEBUGDATA +#undef DEBUGCCW + +#include <linux/sysfs.h> +#include "ctcm_main.h" + +/* + * sysfs attributes + */ + +static ssize_t ctcm_buffer_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct ctcm_priv *priv = dev_get_drvdata(dev); + + if (!priv) + return -ENODEV; + return sprintf(buf, "%d\n", priv->buffer_size); +} + +static ssize_t ctcm_buffer_write(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct net_device *ndev; + int bs1; + struct ctcm_priv *priv = dev_get_drvdata(dev); + + if (!(priv && priv->channel[READ] && + (ndev = priv->channel[READ]->netdev))) { + CTCM_DBF_TEXT(SETUP, CTC_DBF_ERROR, "bfnondev"); + return -ENODEV; + } + + sscanf(buf, "%u", &bs1); + if (bs1 > CTCM_BUFSIZE_LIMIT) + goto einval; + if (bs1 < (576 + LL_HEADER_LENGTH + 2)) + goto einval; + priv->buffer_size = bs1; /* just to overwrite the default */ + + if ((ndev->flags & IFF_RUNNING) && + (bs1 < (ndev->mtu + LL_HEADER_LENGTH + 2))) + goto einval; + + priv->channel[READ]->max_bufsize = bs1; + priv->channel[WRITE]->max_bufsize = bs1; + if (!(ndev->flags & IFF_RUNNING)) + ndev->mtu = bs1 - LL_HEADER_LENGTH - 2; + priv->channel[READ]->flags |= CHANNEL_FLAGS_BUFSIZE_CHANGED; + priv->channel[WRITE]->flags |= CHANNEL_FLAGS_BUFSIZE_CHANGED; + + CTCM_DBF_DEV(SETUP, ndev, buf); + return count; + +einval: + CTCM_DBF_DEV(SETUP, ndev, "buff_err"); + return -EINVAL; +} + +static void ctcm_print_statistics(struct ctcm_priv *priv) +{ + char *sbuf; + char *p; + + if (!priv) + return; + sbuf = kmalloc(2048, GFP_KERNEL); + if (sbuf == NULL) + return; + p = sbuf; + + p += sprintf(p, " Device FSM state: %s\n", + fsm_getstate_str(priv->fsm)); + p += sprintf(p, " RX channel FSM state: %s\n", + fsm_getstate_str(priv->channel[READ]->fsm)); + p += sprintf(p, " TX channel FSM state: %s\n", + fsm_getstate_str(priv->channel[WRITE]->fsm)); + p += sprintf(p, " Max. TX buffer used: %ld\n", + priv->channel[WRITE]->prof.maxmulti); + p += sprintf(p, " Max. chained SKBs: %ld\n", + priv->channel[WRITE]->prof.maxcqueue); + p += sprintf(p, " TX single write ops: %ld\n", + priv->channel[WRITE]->prof.doios_single); + p += sprintf(p, " TX multi write ops: %ld\n", + priv->channel[WRITE]->prof.doios_multi); + p += sprintf(p, " Netto bytes written: %ld\n", + priv->channel[WRITE]->prof.txlen); + p += sprintf(p, " Max. TX IO-time: %ld\n", + priv->channel[WRITE]->prof.tx_time); + + printk(KERN_INFO "Statistics for %s:\n%s", + priv->channel[WRITE]->netdev->name, sbuf); + kfree(sbuf); + return; +} + +static ssize_t stats_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct ctcm_priv *priv = dev_get_drvdata(dev); + if (!priv) + return -ENODEV; + ctcm_print_statistics(priv); + return sprintf(buf, "0\n"); +} + +static ssize_t stats_write(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct ctcm_priv *priv = dev_get_drvdata(dev); + if (!priv) + return -ENODEV; + /* Reset statistics */ + memset(&priv->channel[WRITE]->prof, 0, + sizeof(priv->channel[WRITE]->prof)); + return count; +} + +static ssize_t ctcm_proto_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct ctcm_priv *priv = dev_get_drvdata(dev); + if (!priv) + return -ENODEV; + + return sprintf(buf, "%d\n", priv->protocol); +} + +static ssize_t ctcm_proto_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int value; + struct ctcm_priv *priv = dev_get_drvdata(dev); + + if (!priv) + return -ENODEV; + sscanf(buf, "%u", &value); + if (!((value == CTCM_PROTO_S390) || + (value == CTCM_PROTO_LINUX) || + (value == CTCM_PROTO_MPC) || + (value == CTCM_PROTO_OS390))) + return -EINVAL; + priv->protocol = value; + CTCM_DBF_DEV(SETUP, dev, buf); + + return count; +} + +static ssize_t ctcm_type_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct ccwgroup_device *cgdev; + + cgdev = to_ccwgroupdev(dev); + if (!cgdev) + return -ENODEV; + + return sprintf(buf, "%s\n", + cu3088_type[cgdev->cdev[0]->id.driver_info]); +} + +static DEVICE_ATTR(buffer, 0644, ctcm_buffer_show, ctcm_buffer_write); +static DEVICE_ATTR(protocol, 0644, ctcm_proto_show, ctcm_proto_store); +static DEVICE_ATTR(type, 0444, ctcm_type_show, NULL); +static DEVICE_ATTR(stats, 0644, stats_show, stats_write); + +static struct attribute *ctcm_attr[] = { + &dev_attr_protocol.attr, + &dev_attr_type.attr, + &dev_attr_buffer.attr, + NULL, +}; + +static struct attribute_group ctcm_attr_group = { + .attrs = ctcm_attr, +}; + +int ctcm_add_attributes(struct device *dev) +{ + int rc; + + rc = device_create_file(dev, &dev_attr_stats); + + return rc; +} + +void ctcm_remove_attributes(struct device *dev) +{ + device_remove_file(dev, &dev_attr_stats); +} + +int ctcm_add_files(struct device *dev) +{ + return sysfs_create_group(&dev->kobj, &ctcm_attr_group); +} + +void ctcm_remove_files(struct device *dev) +{ + sysfs_remove_group(&dev->kobj, &ctcm_attr_group); +} + -- ^ permalink raw reply [flat|nested] 8+ messages in thread
* [patch 4/4] ctc: removal of the old ctc driver 2008-02-06 11:55 [patch 0/4] s390: ctc patches for 2.6.25 (2nd try) Ursula Braun ` (2 preceding siblings ...) 2008-02-06 11:55 ` [patch 3/4] ctcm: infrastructure for replaced ctc driver Ursula Braun @ 2008-02-06 11:55 ` Ursula Braun 3 siblings, 0 replies; 8+ messages in thread From: Ursula Braun @ 2008-02-06 11:55 UTC (permalink / raw) To: jgarzik, netdev, linux-s390; +Cc: Peter Tiedemann [-- Attachment #1: 703-ctc_removal.diff --] [-- Type: TEXT/PLAIN, Size: 103820 bytes --] From: Peter Tiedemann <ptiedem@de.ibm.com> ctc driver is replaced by a new ctcm driver. The ctcm driver supports the channel-to-channel connections of the old ctc driver plus an additional MPC protocol to provide SNA connectivity. This patch removes the functions of the old ctc driver. Signed-off-by: Peter Tiedemann <ptiedem@de.ibm.com> Signed-off-by: Ursula Braun <braunu@de.ibm.com> --- drivers/s390/net/ctcdbug.c | 80 - drivers/s390/net/ctcdbug.h | 125 - drivers/s390/net/ctcmain.c | 3054 --------------------------------------------- drivers/s390/net/ctcmain.h | 270 --- 4 files changed, 3529 deletions(-) Index: linux-2.6-uschi/drivers/s390/net/ctcdbug.c =================================================================== --- linux-2.6-uschi.orig/drivers/s390/net/ctcdbug.c +++ /dev/null @@ -1,80 +0,0 @@ -/* - * - * linux/drivers/s390/net/ctcdbug.c - * - * CTC / ESCON network driver - s390 dbf exploit. - * - * Copyright 2000,2003 IBM Corporation - * - * Author(s): Original Code written by - * Peter Tiedemann (ptiedem@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. - */ - -#include "ctcdbug.h" - -/** - * Debug Facility Stuff - */ -debug_info_t *ctc_dbf_setup = NULL; -debug_info_t *ctc_dbf_data = NULL; -debug_info_t *ctc_dbf_trace = NULL; - -DEFINE_PER_CPU(char[256], ctc_dbf_txt_buf); - -void -ctc_unregister_dbf_views(void) -{ - if (ctc_dbf_setup) - debug_unregister(ctc_dbf_setup); - if (ctc_dbf_data) - debug_unregister(ctc_dbf_data); - if (ctc_dbf_trace) - debug_unregister(ctc_dbf_trace); -} -int -ctc_register_dbf_views(void) -{ - ctc_dbf_setup = debug_register(CTC_DBF_SETUP_NAME, - CTC_DBF_SETUP_PAGES, - CTC_DBF_SETUP_NR_AREAS, - CTC_DBF_SETUP_LEN); - ctc_dbf_data = debug_register(CTC_DBF_DATA_NAME, - CTC_DBF_DATA_PAGES, - CTC_DBF_DATA_NR_AREAS, - CTC_DBF_DATA_LEN); - ctc_dbf_trace = debug_register(CTC_DBF_TRACE_NAME, - CTC_DBF_TRACE_PAGES, - CTC_DBF_TRACE_NR_AREAS, - CTC_DBF_TRACE_LEN); - - if ((ctc_dbf_setup == NULL) || (ctc_dbf_data == NULL) || - (ctc_dbf_trace == NULL)) { - ctc_unregister_dbf_views(); - return -ENOMEM; - } - debug_register_view(ctc_dbf_setup, &debug_hex_ascii_view); - debug_set_level(ctc_dbf_setup, CTC_DBF_SETUP_LEVEL); - - debug_register_view(ctc_dbf_data, &debug_hex_ascii_view); - debug_set_level(ctc_dbf_data, CTC_DBF_DATA_LEVEL); - - debug_register_view(ctc_dbf_trace, &debug_hex_ascii_view); - debug_set_level(ctc_dbf_trace, CTC_DBF_TRACE_LEVEL); - - return 0; -} - Index: linux-2.6-uschi/drivers/s390/net/ctcdbug.h =================================================================== --- linux-2.6-uschi.orig/drivers/s390/net/ctcdbug.h +++ /dev/null @@ -1,125 +0,0 @@ -/* - * - * linux/drivers/s390/net/ctcdbug.h - * - * CTC / ESCON network driver - s390 dbf exploit. - * - * Copyright 2000,2003 IBM Corporation - * - * Author(s): Original Code written by - * Peter Tiedemann (ptiedem@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. - */ -#ifndef _CTCDBUG_H_ -#define _CTCDBUG_H_ - -#include <asm/debug.h> -#include "ctcmain.h" -/** - * Debug Facility stuff - */ -#define CTC_DBF_SETUP_NAME "ctc_setup" -#define CTC_DBF_SETUP_LEN 16 -#define CTC_DBF_SETUP_PAGES 8 -#define CTC_DBF_SETUP_NR_AREAS 1 -#define CTC_DBF_SETUP_LEVEL 3 - -#define CTC_DBF_DATA_NAME "ctc_data" -#define CTC_DBF_DATA_LEN 128 -#define CTC_DBF_DATA_PAGES 8 -#define CTC_DBF_DATA_NR_AREAS 1 -#define CTC_DBF_DATA_LEVEL 3 - -#define CTC_DBF_TRACE_NAME "ctc_trace" -#define CTC_DBF_TRACE_LEN 16 -#define CTC_DBF_TRACE_PAGES 4 -#define CTC_DBF_TRACE_NR_AREAS 2 -#define CTC_DBF_TRACE_LEVEL 3 - -#define DBF_TEXT(name,level,text) \ - do { \ - debug_text_event(ctc_dbf_##name,level,text); \ - } while (0) - -#define DBF_HEX(name,level,addr,len) \ - do { \ - debug_event(ctc_dbf_##name,level,(void*)(addr),len); \ - } while (0) - -DECLARE_PER_CPU(char[256], ctc_dbf_txt_buf); -extern debug_info_t *ctc_dbf_setup; -extern debug_info_t *ctc_dbf_data; -extern debug_info_t *ctc_dbf_trace; - - -#define DBF_TEXT_(name,level,text...) \ - do { \ - char* ctc_dbf_txt_buf = get_cpu_var(ctc_dbf_txt_buf); \ - sprintf(ctc_dbf_txt_buf, text); \ - debug_text_event(ctc_dbf_##name,level,ctc_dbf_txt_buf); \ - put_cpu_var(ctc_dbf_txt_buf); \ - } while (0) - -#define DBF_SPRINTF(name,level,text...) \ - do { \ - debug_sprintf_event(ctc_dbf_trace, level, ##text ); \ - debug_sprintf_event(ctc_dbf_trace, level, text ); \ - } while (0) - - -int ctc_register_dbf_views(void); - -void ctc_unregister_dbf_views(void); - -/** - * some more debug stuff - */ - -#define HEXDUMP16(importance,header,ptr) \ -PRINT_##importance(header "%02x %02x %02x %02x %02x %02x %02x %02x " \ - "%02x %02x %02x %02x %02x %02x %02x %02x\n", \ - *(((char*)ptr)),*(((char*)ptr)+1),*(((char*)ptr)+2), \ - *(((char*)ptr)+3),*(((char*)ptr)+4),*(((char*)ptr)+5), \ - *(((char*)ptr)+6),*(((char*)ptr)+7),*(((char*)ptr)+8), \ - *(((char*)ptr)+9),*(((char*)ptr)+10),*(((char*)ptr)+11), \ - *(((char*)ptr)+12),*(((char*)ptr)+13), \ - *(((char*)ptr)+14),*(((char*)ptr)+15)); \ -PRINT_##importance(header "%02x %02x %02x %02x %02x %02x %02x %02x " \ - "%02x %02x %02x %02x %02x %02x %02x %02x\n", \ - *(((char*)ptr)+16),*(((char*)ptr)+17), \ - *(((char*)ptr)+18),*(((char*)ptr)+19), \ - *(((char*)ptr)+20),*(((char*)ptr)+21), \ - *(((char*)ptr)+22),*(((char*)ptr)+23), \ - *(((char*)ptr)+24),*(((char*)ptr)+25), \ - *(((char*)ptr)+26),*(((char*)ptr)+27), \ - *(((char*)ptr)+28),*(((char*)ptr)+29), \ - *(((char*)ptr)+30),*(((char*)ptr)+31)); - -static inline void -hex_dump(unsigned char *buf, size_t len) -{ - size_t i; - - for (i = 0; i < len; i++) { - if (i && !(i % 16)) - printk("\n"); - printk("%02x ", *(buf + i)); - } - printk("\n"); -} - - -#endif Index: linux-2.6-uschi/drivers/s390/net/ctcmain.c =================================================================== --- linux-2.6-uschi.orig/drivers/s390/net/ctcmain.c +++ /dev/null @@ -1,3054 +0,0 @@ -/* - * 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> - Peter Tiedemann (ptiedem@de.ibm.com) - * Driver Model stuff by : Cornelia Huck <cornelia.huck@de.ibm.com> - * - * 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. - * - */ -#undef DEBUG -#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/bitops.h> - -#include <linux/signal.h> -#include <linux/string.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/ccwdev.h> -#include <asm/ccwgroup.h> -#include <asm/uaccess.h> - -#include <asm/idals.h> - -#include "fsm.h" -#include "cu3088.h" - -#include "ctcdbug.h" -#include "ctcmain.h" - -MODULE_AUTHOR("(C) 2000 IBM Corp. by Fritz Elfert (felfert@millenux.com)"); -MODULE_DESCRIPTION("Linux for S/390 CTC/Escon Driver"); -MODULE_LICENSE("GPL"); -/** - * 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!! - */ - CTC_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, - DEV_EVENT_RESTART, - /** - * MUST be always the last element!! - */ - CTC_NR_DEV_EVENTS -}; - -static const char *dev_event_names[] = { - "Start", - "Stop", - "RX up", - "TX up", - "RX down", - "TX down", - "Restart", -}; - -/** - * Events of the channel statemachine - */ -enum ch_events { - /** - * Events, representing return code of - * I/O operations (ccw_device_start, ccw_device_halt 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, -}; - -/** - * 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 int loglevel = CTC_LOGLEVEL_DEFAULT; - -/** - * Linked list of all detected channels. - */ -static struct channel *channels = NULL; - -/** - * Print Banner. - */ -static void -print_banner(void) -{ - static int printed = 0; - - if (printed) - return; - - printk(KERN_INFO "CTC driver initialized\n"); - printed = 1; -} - -/** - * Return type of a detected device. - */ -static enum channel_types -get_channel_type(struct ccw_device_id *id) -{ - enum channel_types type = (enum channel_types) id->driver_info; - - if (type == channel_type_ficon) - type = channel_type_escon; - - return type; -} - -static const char *ch_event_names[] = { - "ccw_device success", - "ccw_device busy", - "ccw_device enodev", - "ccw_device ioerr", - "ccw_device 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", -}; - -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; - struct ll_header *header; - int i; - - if (!(loglevel & CTC_LOGLEVEL_DEBUG)) - return; - p += offset; - bl = *((__u16 *) p); - p += 2; - header = (struct 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"); -} -#else -static inline void -ctc_dump_skb(struct sk_buff *skb, int offset) -{ -} -#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 void -ctc_unpack_skb(struct channel *ch, struct sk_buff *pskb) -{ - struct net_device *dev = ch->netdev; - struct ctc_priv *privptr = (struct ctc_priv *) dev->priv; - __u16 len = *((__u16 *) pskb->data); - - DBF_TEXT(trace, 4, __FUNCTION__); - 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; - struct ll_header *header = (struct ll_header *) pskb->data; - - skb_pull(pskb, LL_HEADER_LENGTH); - if ((ch->protocol == CTC_PROTO_S390) && - (header->type != ETH_P_IP)) { - -#ifndef DEBUG - if (!(ch->logflags & LOG_FLAG_ILLEGALPKT)) { -#endif - /** - * Check packet type only if we stick strictly - * to S/390's protocol of OS390. This only - * supports IP. Otherwise allow any packet - * type. - */ - ctc_pr_warn( - "%s Illegal packet type 0x%04x received, dropping\n", - dev->name, header->type); - ch->logflags |= LOG_FLAG_ILLEGALPKT; -#ifndef DEBUG - } -#endif -#ifdef DEBUG - ctc_dump_skb(pskb, -6); -#endif - privptr->stats.rx_dropped++; - privptr->stats.rx_frame_errors++; - return; - } - pskb->protocol = ntohs(header->type); - if (header->length <= LL_HEADER_LENGTH) { -#ifndef DEBUG - if (!(ch->logflags & LOG_FLAG_ILLEGALSIZE)) { -#endif - ctc_pr_warn( - "%s Illegal packet size %d " - "received (MTU=%d blocklen=%d), " - "dropping\n", dev->name, header->length, - dev->mtu, len); - ch->logflags |= LOG_FLAG_ILLEGALSIZE; -#ifndef DEBUG - } -#endif -#ifdef DEBUG - ctc_dump_skb(pskb, -6); -#endif - privptr->stats.rx_dropped++; - privptr->stats.rx_length_errors++; - return; - } - header->length -= LL_HEADER_LENGTH; - len -= LL_HEADER_LENGTH; - if ((header->length > skb_tailroom(pskb)) || - (header->length > len)) { -#ifndef DEBUG - if (!(ch->logflags & LOG_FLAG_OVERRUN)) { -#endif - ctc_pr_warn( - "%s Illegal packet size %d " - "(beyond the end of received data), " - "dropping\n", dev->name, header->length); - ch->logflags |= LOG_FLAG_OVERRUN; -#ifndef DEBUG - } -#endif -#ifdef DEBUG - ctc_dump_skb(pskb, -6); -#endif - privptr->stats.rx_dropped++; - privptr->stats.rx_length_errors++; - return; - } - skb_put(pskb, header->length); - skb_reset_mac_header(pskb); - len -= header->length; - skb = dev_alloc_skb(pskb->len); - if (!skb) { -#ifndef DEBUG - if (!(ch->logflags & LOG_FLAG_NOMEM)) { -#endif - ctc_pr_warn( - "%s Out of memory in ctc_unpack_skb\n", - dev->name); - ch->logflags |= LOG_FLAG_NOMEM; -#ifndef DEBUG - } -#endif - privptr->stats.rx_dropped++; - return; - } - skb_copy_from_linear_data(pskb, skb_put(skb, pskb->len), - pskb->len); - skb_reset_mac_header(skb); - skb->dev = pskb->dev; - skb->protocol = pskb->protocol; - pskb->ip_summed = CHECKSUM_UNNECESSARY; - /** - * reset logflags - */ - ch->logflags = 0; - privptr->stats.rx_packets++; - privptr->stats.rx_bytes += skb->len; - netif_rx_ni(skb); - dev->last_rx = jiffies; - if (len > 0) { - skb_pull(pskb, header->length); - if (skb_tailroom(pskb) < LL_HEADER_LENGTH) { -#ifndef DEBUG - if (!(ch->logflags & LOG_FLAG_OVERRUN)) { -#endif - ctc_pr_warn( - "%s Overrun in ctc_unpack_skb\n", - dev->name); - ch->logflags |= LOG_FLAG_OVERRUN; -#ifndef DEBUG - } -#endif - return; - } - skb_put(pskb, LL_HEADER_LENGTH); - } - } -} - -/** - * Check return code of a preceeding ccw_device call, halt_IO etc... - * - * @param ch The channel, the error belongs to. - * @param return_code The error code to inspect. - */ -static void -ccw_check_return_code(struct channel *ch, int return_code, char *msg) -{ - DBF_TEXT(trace, 5, __FUNCTION__); - switch (return_code) { - case 0: - fsm_event(ch->fsm, CH_EVENT_IO_SUCCESS, ch); - break; - case -EBUSY: - ctc_pr_warn("%s (%s): Busy !\n", ch->id, msg); - fsm_event(ch->fsm, CH_EVENT_IO_EBUSY, ch); - break; - case -ENODEV: - ctc_pr_emerg("%s (%s): Invalid device called for IO\n", - ch->id, msg); - fsm_event(ch->fsm, CH_EVENT_IO_ENODEV, ch); - break; - case -EIO: - ctc_pr_emerg("%s (%s): Status pending... \n", - ch->id, msg); - fsm_event(ch->fsm, CH_EVENT_IO_EIO, ch); - break; - default: - ctc_pr_emerg("%s (%s): Unknown error in do_IO %04x\n", - ch->id, msg, 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 -ccw_unit_check(struct channel *ch, unsigned char sense) -{ - DBF_TEXT(trace, 5, __FUNCTION__); - if (sense & SNS0_INTERVENTION_REQ) { - if (sense & 0x01) { - ctc_pr_debug("%s: Interface disc. or Sel. reset " - "(remote)\n", ch->id); - fsm_event(ch->fsm, CH_EVENT_UC_RCRESET, ch); - } else { - ctc_pr_debug("%s: System reset (remote)\n", ch->id); - fsm_event(ch->fsm, CH_EVENT_UC_RSRESET, ch); - } - } else if (sense & SNS0_EQUIPMENT_CHECK) { - if (sense & SNS0_BUS_OUT_CHECK) { - ctc_pr_warn("%s: Hardware malfunction (remote)\n", - ch->id); - fsm_event(ch->fsm, CH_EVENT_UC_HWFAIL, ch); - } else { - ctc_pr_warn("%s: Read-data parity error (remote)\n", - ch->id); - fsm_event(ch->fsm, CH_EVENT_UC_RXPARITY, ch); - } - } else if (sense & SNS0_BUS_OUT_CHECK) { - if (sense & 0x04) { - ctc_pr_warn("%s: Data-streaming timeout)\n", ch->id); - fsm_event(ch->fsm, CH_EVENT_UC_TXTIMEOUT, ch); - } else { - ctc_pr_warn("%s: Data-transfer parity error\n", ch->id); - fsm_event(ch->fsm, CH_EVENT_UC_TXPARITY, ch); - } - } else if (sense & SNS0_CMD_REJECT) { - ctc_pr_warn("%s: Command reject\n", ch->id); - } else if (sense == 0) { - ctc_pr_debug("%s: Unit check ZERO\n", ch->id); - fsm_event(ch->fsm, CH_EVENT_UC_ZERO, ch); - } else { - ctc_pr_warn("%s: Unit Check with sense code: %02x\n", - ch->id, 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; - - DBF_TEXT(trace, 5, __FUNCTION__); - - while ((skb = skb_dequeue(q))) { - atomic_dec(&skb->users); - dev_kfree_skb_irq(skb); - } -} - -static int -ctc_checkalloc_buffer(struct channel *ch, int warn) -{ - DBF_TEXT(trace, 5, __FUNCTION__); - 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, - GFP_ATOMIC | GFP_DMA); - if (ch->trans_skb == NULL) { - if (warn) - ctc_pr_warn( - "%s: Couldn't alloc %s trans_skb\n", - ch->id, - (CHANNEL_DIRECTION(ch->flags) == READ) ? - "RX" : "TX"); - return -ENOMEM; - } - ch->ccw[1].count = ch->max_bufsize; - if (set_normalized_cda(&ch->ccw[1], ch->trans_skb->data)) { - dev_kfree_skb(ch->trans_skb); - ch->trans_skb = NULL; - if (warn) - ctc_pr_warn( - "%s: set_normalized_cda for %s " - "trans_skb failed, dropping packets\n", - ch->id, - (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; -} - -/** - * 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) -{ - struct channel *ch = (struct channel *) arg; - struct net_device *dev = ch->netdev; - struct ctc_priv *privptr = dev->priv; - struct sk_buff *skb; - int first = 1; - int i; - unsigned long duration; - struct timespec done_stamp = current_kernel_time(); - - DBF_TEXT(trace, 4, __FUNCTION__); - - duration = - (done_stamp.tv_sec - ch->prof.send_stamp.tv_sec) * 1000000 + - (done_stamp.tv_nsec - ch->prof.send_stamp.tv_nsec) / 1000; - if (duration > ch->prof.tx_time) - ch->prof.tx_time = duration; - - if (ch->irb->scsw.count != 0) - ctc_pr_debug("%s: TX not complete, remaining %d bytes\n", - dev->name, ch->irb->scsw.count); - 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->data = ch->trans_skb_data; - skb_reset_tail_pointer(ch->trans_skb); - 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); - *((__u16 *) skb_put(ch->trans_skb, 2)) = ch->collect_len + 2; - i = 0; - while ((skb = skb_dequeue(&ch->collect_queue))) { - skb_copy_from_linear_data(skb, skb_put(ch->trans_skb, - skb->len), - 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; - spin_unlock(&ch->collect_lock); - ch->ccw[1].count = ch->trans_skb->len; - fsm_addtimer(&ch->timer, CTC_TIMEOUT_5SEC, CH_EVENT_TIMER, ch); - ch->prof.send_stamp = current_kernel_time(); - rc = ccw_device_start(ch->cdev, &ch->ccw[0], - (unsigned long) 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, "chained TX"); - } - } else { - spin_unlock(&ch->collect_lock); - fsm_newstate(fi, CH_STATE_TXIDLE); - } - ctc_clear_busy(dev); -} - -/** - * 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) -{ - struct channel *ch = (struct channel *) arg; - - DBF_TEXT(trace, 4, __FUNCTION__); - fsm_deltimer(&ch->timer); - fsm_newstate(fi, CH_STATE_TXIDLE); - fsm_event(((struct 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) -{ - struct channel *ch = (struct channel *) arg; - struct net_device *dev = ch->netdev; - struct ctc_priv *privptr = dev->priv; - int len = ch->max_bufsize - ch->irb->scsw.count; - struct sk_buff *skb = ch->trans_skb; - __u16 block_len = *((__u16 *) skb->data); - int check_len; - int rc; - - DBF_TEXT(trace, 4, __FUNCTION__); - fsm_deltimer(&ch->timer); - if (len < 8) { - ctc_pr_debug("%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) { - ctc_pr_debug("%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)) { - ctc_pr_debug("%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 = ch->trans_skb_data; - skb_reset_tail_pointer(skb); - skb->len = 0; - if (ctc_checkalloc_buffer(ch, 1)) - return; - ch->ccw[1].count = ch->max_bufsize; - rc = ccw_device_start(ch->cdev, &ch->ccw[0], (unsigned long) ch, 0xff, 0); - if (rc != 0) - ccw_check_return_code(ch, rc, "normal RX"); -} - -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) -{ - struct channel *ch = (struct channel *) arg; - int rc; - - DBF_TEXT(trace, 4, __FUNCTION__); - - if (fsm_getstate(fi) == CH_STATE_TXIDLE) - ctc_pr_debug("%s: remote side issued READ?, init ...\n", ch->id); - 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 { - struct net_device *dev = ch->netdev; - fsm_newstate(fi, CH_STATE_TXIDLE); - fsm_event(((struct 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 */ - - fsm_newstate(fi, (CHANNEL_DIRECTION(ch->flags) == READ) - ? CH_STATE_RXINIT : CH_STATE_TXINIT); - rc = ccw_device_start(ch->cdev, &ch->ccw[0], (unsigned long) ch, 0xff, 0); - if (rc != 0) { - fsm_deltimer(&ch->timer); - fsm_newstate(fi, CH_STATE_SETUPWAIT); - ccw_check_return_code(ch, rc, "init IO"); - } - /** - * 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)) { - struct net_device *dev = ch->netdev; - fsm_event(((struct 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) -{ - struct channel *ch = (struct channel *) arg; - struct net_device *dev = ch->netdev; - __u16 buflen; - int rc; - - DBF_TEXT(trace, 4, __FUNCTION__); - fsm_deltimer(&ch->timer); - buflen = *((__u16 *) ch->trans_skb->data); -#ifdef DEBUG - ctc_pr_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); - rc = ccw_device_start(ch->cdev, &ch->ccw[0], - (unsigned long) ch, 0xff, 0); - if (rc != 0) { - fsm_newstate(fi, CH_STATE_RXINIT); - ccw_check_return_code(ch, rc, "initial RX"); - } else - fsm_event(((struct ctc_priv *) dev->priv)->fsm, - DEV_EVENT_RXUP, dev); - } else { - ctc_pr_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) -{ - struct channel *ch = (struct channel *) arg; - int rc; - unsigned long saveflags; - - DBF_TEXT(trace, 4, __FUNCTION__); - fsm_deltimer(&ch->timer); - fsm_addtimer(&ch->timer, CTC_TIMEOUT_5SEC, CH_EVENT_TIMER, ch); - fsm_newstate(fi, CH_STATE_SETUPWAIT); - saveflags = 0; /* avoids compiler warning with - spin_unlock_irqrestore */ - if (event == CH_EVENT_TIMER) // only for timer not yet locked - spin_lock_irqsave(get_ccwdev_lock(ch->cdev), saveflags); - rc = ccw_device_start(ch->cdev, &ch->ccw[6], (unsigned long) ch, 0xff, 0); - if (event == CH_EVENT_TIMER) - spin_unlock_irqrestore(get_ccwdev_lock(ch->cdev), saveflags); - if (rc != 0) { - fsm_deltimer(&ch->timer); - fsm_newstate(fi, CH_STATE_STARTWAIT); - ccw_check_return_code(ch, rc, "set Mode"); - } 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) -{ - struct channel *ch = (struct channel *) arg; - unsigned long saveflags; - int rc; - struct net_device *dev; - - DBF_TEXT(trace, 4, __FUNCTION__); - if (ch == NULL) { - ctc_pr_warn("ch_action_start ch=NULL\n"); - return; - } - if (ch->netdev == NULL) { - ctc_pr_warn("ch_action_start dev=NULL, id=%s\n", ch->id); - return; - } - dev = ch->netdev; - -#ifdef DEBUG - ctc_pr_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)) { - ctc_pr_notice( - "%s: Could not allocate %s trans_skb, delaying " - "allocation until first transfer\n", - dev->name, - (CHANNEL_DIRECTION(ch->flags) == READ) ? "RX" : "TX"); - } - - 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 (struct ccw1) * 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); - spin_lock_irqsave(get_ccwdev_lock(ch->cdev), saveflags); - rc = ccw_device_halt(ch->cdev, (unsigned long) ch); - spin_unlock_irqrestore(get_ccwdev_lock(ch->cdev), saveflags); - if (rc != 0) { - if (rc != -EBUSY) - fsm_deltimer(&ch->timer); - ccw_check_return_code(ch, rc, "initial HaltIO"); - } -#ifdef DEBUG - ctc_pr_debug("ctc: %s(): leaving\n", __func__); -#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) -{ - struct channel *ch = (struct channel *) arg; - unsigned long saveflags; - int rc; - int oldstate; - - DBF_TEXT(trace, 3, __FUNCTION__); - fsm_deltimer(&ch->timer); - fsm_addtimer(&ch->timer, CTC_TIMEOUT_5SEC, CH_EVENT_TIMER, ch); - saveflags = 0; /* avoids comp warning with - spin_unlock_irqrestore */ - if (event == CH_EVENT_STOP) // only for STOP not yet locked - spin_lock_irqsave(get_ccwdev_lock(ch->cdev), saveflags); - oldstate = fsm_getstate(fi); - fsm_newstate(fi, CH_STATE_TERM); - rc = ccw_device_halt(ch->cdev, (unsigned long) ch); - if (event == CH_EVENT_STOP) - spin_unlock_irqrestore(get_ccwdev_lock(ch->cdev), saveflags); - if (rc != 0) { - if (rc != -EBUSY) { - fsm_deltimer(&ch->timer); - fsm_newstate(fi, oldstate); - } - ccw_check_return_code(ch, rc, "HaltIO in ch_action_haltio"); - } -} - -/** - * 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) -{ - struct channel *ch = (struct channel *) arg; - struct net_device *dev = ch->netdev; - - DBF_TEXT(trace, 3, __FUNCTION__); - 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(((struct 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(((struct 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) -{ - struct channel *ch = (struct channel *) arg; - struct net_device *dev = ch->netdev; - - DBF_TEXT(trace, 3, __FUNCTION__); - fsm_deltimer(&ch->timer); - fsm_newstate(fi, CH_STATE_NOTOP); - if (CHANNEL_DIRECTION(ch->flags) == READ) { - skb_queue_purge(&ch->io_queue); - fsm_event(((struct 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(((struct 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) -{ - struct channel *ch = (struct channel *) arg; - struct net_device *dev = ch->netdev; - - DBF_TEXT(setup, 3, __FUNCTION__); - /** - * 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 = ccw_device_halt(ch->cdev, (unsigned long) ch); - if (rc != 0) - ccw_check_return_code( - ch, rc, "HaltIO in ch_action_setuperr"); - } - return; - } - - ctc_pr_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(((struct ctc_priv *) dev->priv)->fsm, - DEV_EVENT_RXDOWN, dev); - } else { - fsm_newstate(fi, CH_STATE_TXERR); - fsm_event(((struct 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; - - struct channel *ch = (struct channel *) arg; - struct net_device *dev = ch->netdev; - - DBF_TEXT(trace, 3, __FUNCTION__); - fsm_deltimer(&ch->timer); - ctc_pr_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); - saveflags = 0; /* avoids compiler warning with - spin_unlock_irqrestore */ - if (event == CH_EVENT_TIMER) // only for timer not yet locked - spin_lock_irqsave(get_ccwdev_lock(ch->cdev), saveflags); - rc = ccw_device_halt(ch->cdev, (unsigned long) ch); - if (event == CH_EVENT_TIMER) - spin_unlock_irqrestore(get_ccwdev_lock(ch->cdev), saveflags); - if (rc != 0) { - if (rc != -EBUSY) { - fsm_deltimer(&ch->timer); - fsm_newstate(fi, oldstate); - } - ccw_check_return_code(ch, rc, "HaltIO in ch_action_restart"); - } -} - -/** - * 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) -{ - struct channel *ch = (struct channel *) arg; - struct net_device *dev = ch->netdev; - - DBF_TEXT(setup, 3, __FUNCTION__); - if (event == CH_EVENT_TIMER) { - fsm_deltimer(&ch->timer); - ctc_pr_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(((struct ctc_priv *) dev->priv)->fsm, - DEV_EVENT_RXDOWN, dev); - } - } else - ctc_pr_warn("%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) -{ - struct channel *ch = (struct channel *) arg; - struct net_device *dev = ch->netdev; - - DBF_TEXT(setup, 3, __FUNCTION__); - fsm_newstate(fi, CH_STATE_RXERR); - ctc_pr_warn("%s: RX initialization failed\n", dev->name); - ctc_pr_warn("%s: RX <-> RX connection detected\n", dev->name); - fsm_event(((struct 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) -{ - struct channel *ch = (struct channel *) arg; - struct channel *ch2; - struct net_device *dev = ch->netdev; - - DBF_TEXT(trace, 3, __FUNCTION__); - fsm_deltimer(&ch->timer); - ctc_pr_debug("%s: Got remote disconnect, re-initializing ...\n", - dev->name); - - /** - * Notify device statemachine - */ - fsm_event(((struct ctc_priv *) dev->priv)->fsm, DEV_EVENT_RXDOWN, dev); - fsm_event(((struct ctc_priv *) dev->priv)->fsm, DEV_EVENT_TXDOWN, dev); - - fsm_newstate(fi, CH_STATE_DTERM); - ch2 = ((struct ctc_priv *) dev->priv)->channel[WRITE]; - fsm_newstate(ch2->fsm, CH_STATE_DTERM); - - ccw_device_halt(ch->cdev, (unsigned long) ch); - ccw_device_halt(ch2->cdev, (unsigned long) ch2); -} - -/** - * 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) -{ - struct channel *ch = (struct channel *) arg; - struct net_device *dev = ch->netdev; - - DBF_TEXT(setup, 2, __FUNCTION__); - if (event == CH_EVENT_TIMER) { - fsm_deltimer(&ch->timer); - ctc_pr_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(((struct ctc_priv *) dev->priv)->fsm, - DEV_EVENT_TXDOWN, dev); - } - } else - ctc_pr_warn("%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) -{ - struct channel *ch = (struct channel *) arg; - struct net_device *dev = ch->netdev; - unsigned long saveflags; - - DBF_TEXT(trace, 4, __FUNCTION__); - fsm_deltimer(&ch->timer); - if (ch->retry++ > 3) { - ctc_pr_debug("%s: TX retry failed, restarting channel\n", - dev->name); - fsm_event(((struct ctc_priv *) dev->priv)->fsm, - DEV_EVENT_TXDOWN, dev); - ch_action_restart(fi, event, arg); - } else { - struct sk_buff *skb; - - ctc_pr_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], skb->data)) { - ctc_pr_debug( - "%s: IDAL alloc failed, chan restart\n", - dev->name); - fsm_event(((struct 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); - saveflags = 0; /* avoids compiler warning with - spin_unlock_irqrestore */ - if (event == CH_EVENT_TIMER) // only for TIMER not yet locked - spin_lock_irqsave(get_ccwdev_lock(ch->cdev), - saveflags); - rc = ccw_device_start(ch->cdev, &ch->ccw[3], - (unsigned long) ch, 0xff, 0); - if (event == CH_EVENT_TIMER) - spin_unlock_irqrestore(get_ccwdev_lock(ch->cdev), - saveflags); - if (rc != 0) { - fsm_deltimer(&ch->timer); - ccw_check_return_code(ch, rc, "TX in ch_action_txretry"); - 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) -{ - struct channel *ch = (struct channel *) arg; - struct net_device *dev = ch->netdev; - - DBF_TEXT(trace, 3, __FUNCTION__); - fsm_deltimer(&ch->timer); - if (CHANNEL_DIRECTION(ch->flags) == READ) { - ctc_pr_debug("%s: RX I/O error\n", dev->name); - fsm_newstate(fi, CH_STATE_RXERR); - fsm_event(((struct ctc_priv *) dev->priv)->fsm, - DEV_EVENT_RXDOWN, dev); - } else { - ctc_pr_debug("%s: TX I/O error\n", dev->name); - fsm_newstate(fi, CH_STATE_TXERR); - fsm_event(((struct ctc_priv *) dev->priv)->fsm, - DEV_EVENT_TXDOWN, dev); - } -} - -static void -ch_action_reinit(fsm_instance *fi, int event, void *arg) -{ - struct channel *ch = (struct channel *)arg; - struct net_device *dev = ch->netdev; - struct ctc_priv *privptr = dev->priv; - - DBF_TEXT(trace, 4, __FUNCTION__); - ch_action_iofatal(fi, event, arg); - fsm_addtimer(&privptr->restart_timer, 1000, DEV_EVENT_RESTART, 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_reinit }, - {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_reinit }, - {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_reinit }, - {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_reinit }, - {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_reinit }, - {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_reinit }, - {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_reinit }, - {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. - *****************************************************************************/ - -static inline int -less_than(char *id1, char *id2) -{ - int dev1, dev2, i; - - for (i = 0; i < 5; i++) { - id1++; - id2++; - } - dev1 = simple_strtoul(id1, &id1, 16); - dev2 = simple_strtoul(id2, &id2, 16); - - return (dev1 < dev2); -} - -/** - * Add a new channel to the list of channels. - * Keeps the channel list sorted. - * - * @param cdev The ccw_device to be added. - * @param type The type class of the new channel. - * - * @return 0 on success, !0 on error. - */ -static int -add_channel(struct ccw_device *cdev, enum channel_types type) -{ - struct channel **c = &channels; - struct channel *ch; - - DBF_TEXT(trace, 2, __FUNCTION__); - ch = kzalloc(sizeof(struct channel), GFP_KERNEL); - if (!ch) { - ctc_pr_warn("ctc: Out of memory in add_channel\n"); - return -1; - } - /* assure all flags and counters are reset */ - ch->ccw = kzalloc(8 * sizeof(struct ccw1), GFP_KERNEL | GFP_DMA); - if (!ch->ccw) { - kfree(ch); - ctc_pr_warn("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): - * 6: set extended mode - * 7: 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[7].cmd_code = CCW_CMD_NOOP; - ch->ccw[7].flags = CCW_FLAG_SLI; - - ch->cdev = cdev; - snprintf(ch->id, CTC_ID_SIZE, "ch-%s", cdev->dev.bus_id); - ch->type = type; - ch->fsm = init_fsm(ch->id, ch_state_names, - ch_event_names, NR_CH_STATES, NR_CH_EVENTS, - ch_fsm, CH_FSM_LEN, GFP_KERNEL); - if (ch->fsm == NULL) { - ctc_pr_warn("ctc: Could not create FSM in add_channel\n"); - kfree(ch->ccw); - kfree(ch); - return -1; - } - fsm_newstate(ch->fsm, CH_STATE_IDLE); - ch->irb = kzalloc(sizeof(struct irb), GFP_KERNEL); - if (!ch->irb) { - ctc_pr_warn("ctc: Out of memory in add_channel\n"); - kfree_fsm(ch->fsm); - kfree(ch->ccw); - kfree(ch); - return -1; - } - while (*c && less_than((*c)->id, ch->id)) - c = &(*c)->next; - if (*c && (!strncmp((*c)->id, ch->id, CTC_ID_SIZE))) { - ctc_pr_debug( - "ctc: add_channel: device %s already in list, " - "using old entry\n", (*c)->id); - kfree(ch->irb); - kfree_fsm(ch->fsm); - kfree(ch->ccw); - kfree(ch); - return 0; - } - - spin_lock_init(&ch->collect_lock); - - 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; -} - -/** - * Release a specific channel in the channel list. - * - * @param ch Pointer to channel struct to be released. - */ -static void -channel_free(struct 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(struct channel *ch) -{ - struct channel **c = &channels; - - DBF_TEXT(trace, 2, __FUNCTION__); - if (ch == NULL) - return; - - 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); - kfree(ch->irb); - kfree(ch); - return; - } - c = &((*c)->next); - } -} - -/** - * Get a specific channel from the channel list. - * - * @param type Type of channel we are interested in. - * @param id Id 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 struct channel -* -channel_get(enum channel_types type, char *id, int direction) -{ - struct channel *ch = channels; - - DBF_TEXT(trace, 3, __FUNCTION__); -#ifdef DEBUG - ctc_pr_debug("ctc: %s(): searching for ch with id %s and type %d\n", - __func__, id, type); -#endif - - while (ch && ((strncmp(ch->id, id, CTC_ID_SIZE)) || (ch->type != type))) { -#ifdef DEBUG - ctc_pr_debug("ctc: %s(): ch=0x%p (id=%s, type=%d\n", - __func__, ch, ch->id, ch->type); -#endif - ch = ch->next; - } -#ifdef DEBUG - ctc_pr_debug("ctc: %s(): ch=0x%pq (id=%s, type=%d\n", - __func__, ch, ch->id, ch->type); -#endif - if (!ch) { - ctc_pr_warn("ctc: %s(): channel with id %s " - "and type %d not found in channel list\n", - __func__, id, 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; -} - -/** - * Return the channel type by name. - * - * @param name Name of network interface. - * - * @return Type class of channel to be used for that interface. - */ -static enum channel_types inline -extract_channel_media(char *name) -{ - enum channel_types ret = channel_type_unknown; - - if (name != NULL) { - if (strncmp(name, "ctc", 3) == 0) - ret = channel_type_parallel; - if (strncmp(name, "escon", 5) == 0) - ret = channel_type_escon; - } - return ret; -} - -static long -__ctc_check_irb_error(struct ccw_device *cdev, struct irb *irb) -{ - if (!IS_ERR(irb)) - return 0; - - switch (PTR_ERR(irb)) { - case -EIO: - ctc_pr_warn("i/o-error on device %s\n", cdev->dev.bus_id); -// CTC_DBF_TEXT(trace, 2, "ckirberr"); -// CTC_DBF_TEXT_(trace, 2, " rc%d", -EIO); - break; - case -ETIMEDOUT: - ctc_pr_warn("timeout on device %s\n", cdev->dev.bus_id); -// CTC_DBF_TEXT(trace, 2, "ckirberr"); -// CTC_DBF_TEXT_(trace, 2, " rc%d", -ETIMEDOUT); - break; - default: - ctc_pr_warn("unknown error %ld on device %s\n", PTR_ERR(irb), - cdev->dev.bus_id); -// CTC_DBF_TEXT(trace, 2, "ckirberr"); -// CTC_DBF_TEXT(trace, 2, " rc???"); - } - return PTR_ERR(irb); -} - -/** - * Main IRQ handler. - * - * @param cdev The ccw_device the interrupt is for. - * @param intparm interruption parameter. - * @param irb interruption response block. - */ -static void -ctc_irq_handler(struct ccw_device *cdev, unsigned long intparm, struct irb *irb) -{ - struct channel *ch; - struct net_device *dev; - struct ctc_priv *priv; - - DBF_TEXT(trace, 5, __FUNCTION__); - if (__ctc_check_irb_error(cdev, irb)) - return; - - /* Check for unsolicited interrupts. */ - if (!cdev->dev.driver_data) { - ctc_pr_warn("ctc: Got unsolicited irq: %s c-%02x d-%02x\n", - cdev->dev.bus_id, irb->scsw.cstat, - irb->scsw.dstat); - return; - } - - priv = ((struct ccwgroup_device *)cdev->dev.driver_data) - ->dev.driver_data; - - /* Try to extract channel from driver data. */ - if (priv->channel[READ]->cdev == cdev) - ch = priv->channel[READ]; - else if (priv->channel[WRITE]->cdev == cdev) - ch = priv->channel[WRITE]; - else { - ctc_pr_err("ctc: Can't determine channel for interrupt, " - "device %s\n", cdev->dev.bus_id); - return; - } - - dev = (struct net_device *) (ch->netdev); - if (dev == NULL) { - ctc_pr_crit("ctc: ctc_irq_handler dev=NULL bus_id=%s, ch=0x%p\n", - cdev->dev.bus_id, ch); - return; - } - -#ifdef DEBUG - ctc_pr_debug("%s: interrupt for device: %s received c-%02x d-%02x\n", - dev->name, ch->id, irb->scsw.cstat, irb->scsw.dstat); -#endif - - /* Copy interruption response block. */ - memcpy(ch->irb, irb, sizeof(struct irb)); - - /* Check for good subchannel return code, otherwise error message */ - if (ch->irb->scsw.cstat) { - fsm_event(ch->fsm, CH_EVENT_SC_UNKNOWN, ch); - ctc_pr_warn("%s: subchannel check for device: %s - %02x %02x\n", - dev->name, ch->id, ch->irb->scsw.cstat, - ch->irb->scsw.dstat); - return; - } - - /* Check the reason-code of a unit check */ - if (ch->irb->scsw.dstat & DEV_STAT_UNIT_CHECK) { - ccw_unit_check(ch, ch->irb->ecw[0]); - return; - } - if (ch->irb->scsw.dstat & DEV_STAT_BUSY) { - if (ch->irb->scsw.dstat & DEV_STAT_ATTENTION) - fsm_event(ch->fsm, CH_EVENT_ATTNBUSY, ch); - else - fsm_event(ch->fsm, CH_EVENT_BUSY, ch); - return; - } - if (ch->irb->scsw.dstat & DEV_STAT_ATTENTION) { - fsm_event(ch->fsm, CH_EVENT_ATTN, ch); - return; - } - if ((ch->irb->scsw.stctl & SCSW_STCTL_SEC_STATUS) || - (ch->irb->scsw.stctl == SCSW_STCTL_STATUS_PEND) || - (ch->irb->scsw.stctl == - (SCSW_STCTL_ALERT_STATUS | SCSW_STCTL_STATUS_PEND))) - fsm_event(ch->fsm, CH_EVENT_FINSTAT, ch); - else - fsm_event(ch->fsm, CH_EVENT_IRQ, ch); - -} - -/** - * 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 struct net_device * upon call. - */ -static void -dev_action_start(fsm_instance * fi, int event, void *arg) -{ - struct net_device *dev = (struct net_device *) arg; - struct ctc_priv *privptr = dev->priv; - int direction; - - DBF_TEXT(setup, 3, __FUNCTION__); - fsm_deltimer(&privptr->restart_timer); - fsm_newstate(fi, DEV_STATE_STARTWAIT_RXTX); - for (direction = READ; direction <= WRITE; direction++) { - struct 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 struct net_device * upon call. - */ -static void -dev_action_stop(fsm_instance * fi, int event, void *arg) -{ - struct net_device *dev = (struct net_device *) arg; - struct ctc_priv *privptr = dev->priv; - int direction; - - DBF_TEXT(trace, 3, __FUNCTION__); - fsm_newstate(fi, DEV_STATE_STOPWAIT_RXTX); - for (direction = READ; direction <= WRITE; direction++) { - struct channel *ch = privptr->channel[direction]; - fsm_event(ch->fsm, CH_EVENT_STOP, ch); - } -} -static void -dev_action_restart(fsm_instance *fi, int event, void *arg) -{ - struct net_device *dev = (struct net_device *)arg; - struct ctc_priv *privptr = dev->priv; - - DBF_TEXT(trace, 3, __FUNCTION__); - ctc_pr_debug("%s: Restarting\n", dev->name); - dev_action_stop(fi, event, arg); - fsm_event(privptr->fsm, DEV_EVENT_STOP, dev); - fsm_addtimer(&privptr->restart_timer, CTC_TIMEOUT_5SEC, - DEV_EVENT_START, dev); -} - -/** - * 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 struct net_device * upon call. - */ -static void -dev_action_chup(fsm_instance * fi, int event, void *arg) -{ - struct net_device *dev = (struct net_device *) arg; - - DBF_TEXT(trace, 3, __FUNCTION__); - 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); - ctc_pr_info("%s: connected with remote side\n", - dev->name); - ctc_clear_busy(dev); - } - break; - case DEV_STATE_STARTWAIT_TX: - if (event == DEV_EVENT_TXUP) { - fsm_newstate(fi, DEV_STATE_RUNNING); - ctc_pr_info("%s: connected with remote side\n", - dev->name); - ctc_clear_busy(dev); - } - break; - case DEV_STATE_STOPWAIT_TX: - if (event == DEV_EVENT_RXUP) - fsm_newstate(fi, DEV_STATE_STOPWAIT_RXTX); - break; - case DEV_STATE_STOPWAIT_RX: - if (event == DEV_EVENT_TXUP) - fsm_newstate(fi, DEV_STATE_STOPWAIT_RXTX); - break; - } -} - -/** - * Called from channel statemachine - * when a channel has been shutdown. - * - * @param fi An instance of an interface statemachine. - * @param event The event, just happened. - * @param arg Generic pointer, casted from struct net_device * upon call. - */ -static void -dev_action_chdown(fsm_instance * fi, int event, void *arg) -{ - - DBF_TEXT(trace, 3, __FUNCTION__); - switch (fsm_getstate(fi)) { - case DEV_STATE_RUNNING: - if (event == DEV_EVENT_TXDOWN) - fsm_newstate(fi, DEV_STATE_STARTWAIT_TX); - else - fsm_newstate(fi, DEV_STATE_STARTWAIT_RX); - break; - case DEV_STATE_STARTWAIT_RX: - if (event == DEV_EVENT_TXDOWN) - fsm_newstate(fi, DEV_STATE_STARTWAIT_RXTX); - break; - case DEV_STATE_STARTWAIT_TX: - if (event == DEV_EVENT_RXDOWN) - fsm_newstate(fi, DEV_STATE_STARTWAIT_RXTX); - break; - case DEV_STATE_STOPWAIT_RXTX: - if (event == DEV_EVENT_TXDOWN) - fsm_newstate(fi, DEV_STATE_STOPWAIT_RX); - else - fsm_newstate(fi, DEV_STATE_STOPWAIT_TX); - break; - case DEV_STATE_STOPWAIT_RX: - if (event == DEV_EVENT_RXDOWN) - fsm_newstate(fi, DEV_STATE_STOPPED); - break; - case DEV_STATE_STOPWAIT_TX: - if (event == DEV_EVENT_TXDOWN) - fsm_newstate(fi, DEV_STATE_STOPPED); - break; - } -} - -static const fsm_node dev_fsm[] = { - {DEV_STATE_STOPPED, DEV_EVENT_START, dev_action_start}, - - {DEV_STATE_STOPWAIT_RXTX, DEV_EVENT_START, dev_action_start }, - {DEV_STATE_STOPWAIT_RXTX, DEV_EVENT_RXDOWN, dev_action_chdown }, - {DEV_STATE_STOPWAIT_RXTX, DEV_EVENT_TXDOWN, dev_action_chdown }, - {DEV_STATE_STOPWAIT_RXTX, DEV_EVENT_RESTART, dev_action_restart }, - - {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_RX, DEV_EVENT_RESTART, dev_action_restart }, - - {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_STOPWAIT_TX, DEV_EVENT_RESTART, dev_action_restart }, - - {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_RXTX, DEV_EVENT_RESTART, dev_action_restart }, - - {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_TX, DEV_EVENT_RESTART, dev_action_restart }, - - {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_STARTWAIT_RX, DEV_EVENT_RESTART, dev_action_restart }, - - {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 }, - {DEV_STATE_RUNNING, DEV_EVENT_RESTART, dev_action_restart }, -}; - -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(struct channel *ch, struct sk_buff *skb) -{ - unsigned long saveflags; - struct ll_header header; - int rc = 0; - - DBF_TEXT(trace, 5, __FUNCTION__); - /* we need to acquire the lock for testing the state - * otherwise we can have an IRQ changing the state to - * TXIDLE after the test but before acquiring the lock. - */ - spin_lock_irqsave(&ch->collect_lock, saveflags); - if (fsm_getstate(ch->fsm) != CH_STATE_TXIDLE) { - int l = skb->len + LL_HEADER_LENGTH; - - if (ch->collect_len + l > ch->max_bufsize - 2) { - spin_unlock_irqrestore(&ch->collect_lock, saveflags); - return -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; - struct sk_buff *nskb; - unsigned long hi; - spin_unlock_irqrestore(&ch->collect_lock, saveflags); - /** - * 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; - - /** - * IDAL support in CTC is broken, so we have to - * care about skb's above 2G ourselves. - */ - hi = ((unsigned long)skb_tail_pointer(skb) + - LL_HEADER_LENGTH) >> 31; - if (hi) { - nskb = alloc_skb(skb->len, GFP_ATOMIC | GFP_DMA); - if (!nskb) { - atomic_dec(&skb->users); - skb_pull(skb, LL_HEADER_LENGTH + 2); - ctc_clear_busy(ch->netdev); - return -ENOMEM; - } else { - memcpy(skb_put(nskb, skb->len), - skb->data, skb->len); - atomic_inc(&nskb->users); - atomic_dec(&skb->users); - dev_kfree_skb_irq(skb); - skb = nskb; - } - } - - ch->ccw[4].count = block_len; - if (set_normalized_cda(&ch->ccw[4], 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. - */ - atomic_dec(&skb->users); - skb_pull(skb, LL_HEADER_LENGTH + 2); - ctc_clear_busy(ch->netdev); - return -EBUSY; - } - - skb_reset_tail_pointer(ch->trans_skb); - ch->trans_skb->len = 0; - ch->ccw[1].count = skb->len; - skb_copy_from_linear_data(skb, skb_put(ch->trans_skb, - skb->len), - 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; - fsm_newstate(ch->fsm, CH_STATE_TX); - fsm_addtimer(&ch->timer, CTC_TIMEOUT_5SEC, CH_EVENT_TIMER, ch); - spin_lock_irqsave(get_ccwdev_lock(ch->cdev), saveflags); - ch->prof.send_stamp = current_kernel_time(); - rc = ccw_device_start(ch->cdev, &ch->ccw[ccw_idx], - (unsigned long) ch, 0xff, 0); - spin_unlock_irqrestore(get_ccwdev_lock(ch->cdev), saveflags); - if (ccw_idx == 3) - ch->prof.doios_single++; - if (rc != 0) { - fsm_deltimer(&ch->timer); - ccw_check_return_code(ch, rc, "single skb TX"); - 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) { - struct net_device *dev = ch->netdev; - struct ctc_priv *privptr = dev->priv; - privptr->stats.tx_packets++; - privptr->stats.tx_bytes += - skb->len - LL_HEADER_LENGTH; - } - } - } - - ctc_clear_busy(ch->netdev); - 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(struct net_device * dev) -{ - DBF_TEXT(trace, 5, __FUNCTION__); - fsm_event(((struct 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(struct net_device * dev) -{ - DBF_TEXT(trace, 5, __FUNCTION__); - fsm_event(((struct ctc_priv *) dev->priv)->fsm, DEV_EVENT_STOP, dev); - 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, struct net_device * dev) -{ - int rc = 0; - struct ctc_priv *privptr = (struct ctc_priv *) dev->priv; - - DBF_TEXT(trace, 5, __FUNCTION__); - /** - * Some sanity checks ... - */ - if (skb == NULL) { - ctc_pr_warn("%s: NULL sk_buff passed\n", dev->name); - privptr->stats.tx_dropped++; - return 0; - } - if (skb_headroom(skb) < (LL_HEADER_LENGTH + 2)) { - ctc_pr_warn("%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 - * and throw away packet. - */ - if (fsm_getstate(privptr->fsm) != DEV_STATE_RUNNING) { - fsm_event(privptr->fsm, DEV_EVENT_START, dev); - 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; - 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(struct net_device * dev, int new_mtu) -{ - struct ctc_priv *privptr = (struct ctc_priv *) dev->priv; - - DBF_TEXT(trace, 3, __FUNCTION__); - 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(struct net_device * dev) -{ - return &((struct ctc_priv *) dev->priv)->stats; -} - -/* - * sysfs attributes - */ - -static ssize_t -buffer_show(struct device *dev, struct device_attribute *attr, char *buf) -{ - struct ctc_priv *priv; - - priv = dev->driver_data; - if (!priv) - return -ENODEV; - return sprintf(buf, "%d\n", - priv->buffer_size); -} - -static ssize_t -buffer_write(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) -{ - struct ctc_priv *priv; - struct net_device *ndev; - int bs1; - char buffer[16]; - - DBF_TEXT(trace, 3, __FUNCTION__); - DBF_TEXT(trace, 3, buf); - priv = dev->driver_data; - if (!priv) { - DBF_TEXT(trace, 3, "bfnopriv"); - return -ENODEV; - } - - sscanf(buf, "%u", &bs1); - if (bs1 > CTC_BUFSIZE_LIMIT) - goto einval; - if (bs1 < (576 + LL_HEADER_LENGTH + 2)) - goto einval; - priv->buffer_size = bs1; // just to overwrite the default - - ndev = priv->channel[READ]->netdev; - if (!ndev) { - DBF_TEXT(trace, 3, "bfnondev"); - return -ENODEV; - } - - if ((ndev->flags & IFF_RUNNING) && - (bs1 < (ndev->mtu + LL_HEADER_LENGTH + 2))) - goto einval; - - priv->channel[READ]->max_bufsize = bs1; - priv->channel[WRITE]->max_bufsize = bs1; - if (!(ndev->flags & IFF_RUNNING)) - ndev->mtu = bs1 - LL_HEADER_LENGTH - 2; - priv->channel[READ]->flags |= CHANNEL_FLAGS_BUFSIZE_CHANGED; - priv->channel[WRITE]->flags |= CHANNEL_FLAGS_BUFSIZE_CHANGED; - - sprintf(buffer, "%d",priv->buffer_size); - DBF_TEXT(trace, 3, buffer); - return count; - -einval: - DBF_TEXT(trace, 3, "buff_err"); - return -EINVAL; -} - -static ssize_t -loglevel_show(struct device *dev, struct device_attribute *attr, char *buf) -{ - return sprintf(buf, "%d\n", loglevel); -} - -static ssize_t -loglevel_write(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) -{ - int ll1; - - DBF_TEXT(trace, 5, __FUNCTION__); - sscanf(buf, "%i", &ll1); - - if ((ll1 > CTC_LOGLEVEL_MAX) || (ll1 < 0)) - return -EINVAL; - loglevel = ll1; - return count; -} - -static void -ctc_print_statistics(struct ctc_priv *priv) -{ - char *sbuf; - char *p; - - DBF_TEXT(trace, 4, __FUNCTION__); - if (!priv) - return; - sbuf = kmalloc(2048, GFP_KERNEL); - if (sbuf == NULL) - return; - p = sbuf; - - p += sprintf(p, " Device FSM state: %s\n", - fsm_getstate_str(priv->fsm)); - p += sprintf(p, " RX channel FSM state: %s\n", - fsm_getstate_str(priv->channel[READ]->fsm)); - p += sprintf(p, " TX channel FSM state: %s\n", - fsm_getstate_str(priv->channel[WRITE]->fsm)); - p += sprintf(p, " Max. TX buffer used: %ld\n", - priv->channel[WRITE]->prof.maxmulti); - p += sprintf(p, " Max. chained SKBs: %ld\n", - priv->channel[WRITE]->prof.maxcqueue); - p += sprintf(p, " TX single write ops: %ld\n", - priv->channel[WRITE]->prof.doios_single); - p += sprintf(p, " TX multi write ops: %ld\n", - priv->channel[WRITE]->prof.doios_multi); - p += sprintf(p, " Netto bytes written: %ld\n", - priv->channel[WRITE]->prof.txlen); - p += sprintf(p, " Max. TX IO-time: %ld\n", - priv->channel[WRITE]->prof.tx_time); - - ctc_pr_debug("Statistics for %s:\n%s", - priv->channel[WRITE]->netdev->name, sbuf); - kfree(sbuf); - return; -} - -static ssize_t -stats_show(struct device *dev, struct device_attribute *attr, char *buf) -{ - struct ctc_priv *priv = dev->driver_data; - if (!priv) - return -ENODEV; - ctc_print_statistics(priv); - return sprintf(buf, "0\n"); -} - -static ssize_t -stats_write(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) -{ - struct ctc_priv *priv = dev->driver_data; - if (!priv) - return -ENODEV; - /* Reset statistics */ - memset(&priv->channel[WRITE]->prof, 0, - sizeof(priv->channel[WRITE]->prof)); - return count; -} - -static void -ctc_netdev_unregister(struct net_device * dev) -{ - struct ctc_priv *privptr; - - if (!dev) - return; - privptr = (struct ctc_priv *) dev->priv; - unregister_netdev(dev); -} - -static int -ctc_netdev_register(struct net_device * dev) -{ - return register_netdev(dev); -} - -static void -ctc_free_netdevice(struct net_device * dev, int free_dev) -{ - struct ctc_priv *privptr; - if (!dev) - return; - privptr = dev->priv; - if (privptr) { - if (privptr->fsm) - kfree_fsm(privptr->fsm); - kfree(privptr); - } -#ifdef MODULE - if (free_dev) - free_netdev(dev); -#endif -} - -static ssize_t -ctc_proto_show(struct device *dev, struct device_attribute *attr, char *buf) -{ - struct ctc_priv *priv; - - priv = dev->driver_data; - if (!priv) - return -ENODEV; - - return sprintf(buf, "%d\n", priv->protocol); -} - -static ssize_t -ctc_proto_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) -{ - struct ctc_priv *priv; - int value; - - DBF_TEXT(trace, 3, __FUNCTION__); - pr_debug("%s() called\n", __FUNCTION__); - - priv = dev->driver_data; - if (!priv) - return -ENODEV; - sscanf(buf, "%u", &value); - if (!((value == CTC_PROTO_S390) || - (value == CTC_PROTO_LINUX) || - (value == CTC_PROTO_OS390))) - return -EINVAL; - priv->protocol = value; - - return count; -} - -static ssize_t -ctc_type_show(struct device *dev, struct device_attribute *attr, char *buf) -{ - struct ccwgroup_device *cgdev; - - cgdev = to_ccwgroupdev(dev); - if (!cgdev) - return -ENODEV; - - return sprintf(buf, "%s\n", cu3088_type[cgdev->cdev[0]->id.driver_info]); -} - -static DEVICE_ATTR(buffer, 0644, buffer_show, buffer_write); -static DEVICE_ATTR(protocol, 0644, ctc_proto_show, ctc_proto_store); -static DEVICE_ATTR(type, 0444, ctc_type_show, NULL); - -static DEVICE_ATTR(loglevel, 0644, loglevel_show, loglevel_write); -static DEVICE_ATTR(stats, 0644, stats_show, stats_write); - -static struct attribute *ctc_attr[] = { - &dev_attr_protocol.attr, - &dev_attr_type.attr, - &dev_attr_buffer.attr, - NULL, -}; - -static struct attribute_group ctc_attr_group = { - .attrs = ctc_attr, -}; - -static int -ctc_add_attributes(struct device *dev) -{ - int rc; - - rc = device_create_file(dev, &dev_attr_loglevel); - if (rc) - goto out; - rc = device_create_file(dev, &dev_attr_stats); - if (!rc) - goto out; - device_remove_file(dev, &dev_attr_loglevel); -out: - return rc; -} - -static void -ctc_remove_attributes(struct device *dev) -{ - device_remove_file(dev, &dev_attr_stats); - device_remove_file(dev, &dev_attr_loglevel); -} - -static int -ctc_add_files(struct device *dev) -{ - pr_debug("%s() called\n", __FUNCTION__); - - return sysfs_create_group(&dev->kobj, &ctc_attr_group); -} - -static void -ctc_remove_files(struct device *dev) -{ - pr_debug("%s() called\n", __FUNCTION__); - - sysfs_remove_group(&dev->kobj, &ctc_attr_group); -} - -/** - * Add ctc specific attributes. - * Add ctc private data. - * - * @param cgdev pointer to ccwgroup_device just added - * - * @returns 0 on success, !0 on failure. - */ -static int -ctc_probe_device(struct ccwgroup_device *cgdev) -{ - struct ctc_priv *priv; - int rc; - char buffer[16]; - - pr_debug("%s() called\n", __FUNCTION__); - DBF_TEXT(setup, 3, __FUNCTION__); - - if (!get_device(&cgdev->dev)) - return -ENODEV; - - priv = kzalloc(sizeof(struct ctc_priv), GFP_KERNEL); - if (!priv) { - ctc_pr_err("%s: Out of memory\n", __func__); - put_device(&cgdev->dev); - return -ENOMEM; - } - - rc = ctc_add_files(&cgdev->dev); - if (rc) { - kfree(priv); - put_device(&cgdev->dev); - return rc; - } - priv->buffer_size = CTC_BUFSIZE_DEFAULT; - cgdev->cdev[0]->handler = ctc_irq_handler; - cgdev->cdev[1]->handler = ctc_irq_handler; - cgdev->dev.driver_data = priv; - - sprintf(buffer, "%p", priv); - DBF_TEXT(data, 3, buffer); - - sprintf(buffer, "%u", (unsigned int)sizeof(struct ctc_priv)); - DBF_TEXT(data, 3, buffer); - - sprintf(buffer, "%p", &channels); - DBF_TEXT(data, 3, buffer); - - sprintf(buffer, "%u", (unsigned int)sizeof(struct channel)); - DBF_TEXT(data, 3, buffer); - - return 0; -} - -/** - * Device setup function called by alloc_netdev(). - * - * @param dev Device to be setup. - */ -void ctc_init_netdevice(struct net_device * dev) -{ - DBF_TEXT(setup, 3, __FUNCTION__); - - if (dev->mtu == 0) - 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; - dev->flags = IFF_POINTOPOINT | IFF_NOARP; -} - - -/** - * - * Setup an interface. - * - * @param cgdev Device to be setup. - * - * @returns 0 on success, !0 on failure. - */ -static int -ctc_new_device(struct ccwgroup_device *cgdev) -{ - char read_id[CTC_ID_SIZE]; - char write_id[CTC_ID_SIZE]; - int direction; - enum channel_types type; - struct ctc_priv *privptr; - struct net_device *dev; - int ret; - char buffer[16]; - - pr_debug("%s() called\n", __FUNCTION__); - DBF_TEXT(setup, 3, __FUNCTION__); - - privptr = cgdev->dev.driver_data; - if (!privptr) - return -ENODEV; - - sprintf(buffer, "%d", privptr->buffer_size); - DBF_TEXT(setup, 3, buffer); - - type = get_channel_type(&cgdev->cdev[0]->id); - - snprintf(read_id, CTC_ID_SIZE, "ch-%s", cgdev->cdev[0]->dev.bus_id); - snprintf(write_id, CTC_ID_SIZE, "ch-%s", cgdev->cdev[1]->dev.bus_id); - - if (add_channel(cgdev->cdev[0], type)) - return -ENOMEM; - if (add_channel(cgdev->cdev[1], type)) - return -ENOMEM; - - ret = ccw_device_set_online(cgdev->cdev[0]); - if (ret != 0) { - printk(KERN_WARNING - "ccw_device_set_online (cdev[0]) failed with ret = %d\n", ret); - } - - ret = ccw_device_set_online(cgdev->cdev[1]); - if (ret != 0) { - printk(KERN_WARNING - "ccw_device_set_online (cdev[1]) failed with ret = %d\n", ret); - } - - dev = alloc_netdev(0, "ctc%d", ctc_init_netdevice); - if (!dev) { - ctc_pr_warn("ctc_init_netdevice failed\n"); - goto out; - } - dev->priv = privptr; - - privptr->fsm = init_fsm("ctcdev", dev_state_names, - dev_event_names, CTC_NR_DEV_STATES, CTC_NR_DEV_EVENTS, - dev_fsm, DEV_FSM_LEN, GFP_KERNEL); - if (privptr->fsm == NULL) { - free_netdev(dev); - goto out; - } - fsm_newstate(privptr->fsm, DEV_STATE_STOPPED); - fsm_settimer(privptr->fsm, &privptr->restart_timer); - - for (direction = READ; direction <= WRITE; direction++) { - privptr->channel[direction] = - channel_get(type, direction == READ ? read_id : write_id, - direction); - if (privptr->channel[direction] == NULL) { - if (direction == WRITE) - channel_free(privptr->channel[READ]); - - ctc_free_netdevice(dev, 1); - goto out; - } - privptr->channel[direction]->netdev = dev; - privptr->channel[direction]->protocol = privptr->protocol; - privptr->channel[direction]->max_bufsize = privptr->buffer_size; - } - /* sysfs magic */ - SET_NETDEV_DEV(dev, &cgdev->dev); - - if (ctc_netdev_register(dev) != 0) { - ctc_free_netdevice(dev, 1); - goto out; - } - - if (ctc_add_attributes(&cgdev->dev)) { - ctc_netdev_unregister(dev); - dev->priv = NULL; - ctc_free_netdevice(dev, 1); - goto out; - } - - strlcpy(privptr->fsm->name, dev->name, sizeof (privptr->fsm->name)); - - print_banner(); - - ctc_pr_info("%s: read: %s, write: %s, proto: %d\n", - dev->name, privptr->channel[READ]->id, - privptr->channel[WRITE]->id, privptr->protocol); - - return 0; -out: - ccw_device_set_offline(cgdev->cdev[1]); - ccw_device_set_offline(cgdev->cdev[0]); - - return -ENODEV; -} - -/** - * Shutdown an interface. - * - * @param cgdev Device to be shut down. - * - * @returns 0 on success, !0 on failure. - */ -static int -ctc_shutdown_device(struct ccwgroup_device *cgdev) -{ - struct ctc_priv *priv; - struct net_device *ndev; - - DBF_TEXT(setup, 3, __FUNCTION__); - pr_debug("%s() called\n", __FUNCTION__); - - - priv = cgdev->dev.driver_data; - ndev = NULL; - if (!priv) - return -ENODEV; - - if (priv->channel[READ]) { - ndev = priv->channel[READ]->netdev; - - /* Close the device */ - ctc_close(ndev); - ndev->flags &=~IFF_RUNNING; - - ctc_remove_attributes(&cgdev->dev); - - channel_free(priv->channel[READ]); - } - if (priv->channel[WRITE]) - channel_free(priv->channel[WRITE]); - - if (ndev) { - ctc_netdev_unregister(ndev); - ndev->priv = NULL; - ctc_free_netdevice(ndev, 1); - } - - if (priv->fsm) - kfree_fsm(priv->fsm); - - ccw_device_set_offline(cgdev->cdev[1]); - ccw_device_set_offline(cgdev->cdev[0]); - - if (priv->channel[READ]) - channel_remove(priv->channel[READ]); - if (priv->channel[WRITE]) - channel_remove(priv->channel[WRITE]); - priv->channel[READ] = priv->channel[WRITE] = NULL; - - return 0; - -} - -static void -ctc_remove_device(struct ccwgroup_device *cgdev) -{ - struct ctc_priv *priv; - - pr_debug("%s() called\n", __FUNCTION__); - DBF_TEXT(setup, 3, __FUNCTION__); - - priv = cgdev->dev.driver_data; - if (!priv) - return; - if (cgdev->state == CCWGROUP_ONLINE) - ctc_shutdown_device(cgdev); - ctc_remove_files(&cgdev->dev); - cgdev->dev.driver_data = NULL; - kfree(priv); - put_device(&cgdev->dev); -} - -static struct ccwgroup_driver ctc_group_driver = { - .owner = THIS_MODULE, - .name = "ctc", - .max_slaves = 2, - .driver_id = 0xC3E3C3, - .probe = ctc_probe_device, - .remove = ctc_remove_device, - .set_online = ctc_new_device, - .set_offline = ctc_shutdown_device, -}; - -/** - * Module related routines - *****************************************************************************/ - -/** - * 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. - */ -static void __exit -ctc_exit(void) -{ - DBF_TEXT(setup, 3, __FUNCTION__); - unregister_cu3088_discipline(&ctc_group_driver); - ctc_unregister_dbf_views(); - ctc_pr_info("CTC driver unloaded\n"); -} - -/** - * Initialize module. - * This is called just after the module is loaded. - * - * @return 0 on success, !0 on error. - */ -static int __init -ctc_init(void) -{ - int ret = 0; - - loglevel = CTC_LOGLEVEL_DEFAULT; - - DBF_TEXT(setup, 3, __FUNCTION__); - - print_banner(); - - ret = ctc_register_dbf_views(); - if (ret){ - ctc_pr_crit("ctc_init failed with ctc_register_dbf_views rc = %d\n", ret); - return ret; - } - ret = register_cu3088_discipline(&ctc_group_driver); - if (ret) { - ctc_unregister_dbf_views(); - } - return ret; -} - -module_init(ctc_init); -module_exit(ctc_exit); - -/* --- This is the END my friend --- */ Index: linux-2.6-uschi/drivers/s390/net/ctcmain.h =================================================================== --- linux-2.6-uschi.orig/drivers/s390/net/ctcmain.h +++ /dev/null @@ -1,270 +0,0 @@ -/* - * CTC / ESCON network driver - * - * Copyright (C) 2001 IBM Deutschland Entwicklung GmbH, IBM Corporation - * Author(s): Fritz Elfert (elfert@de.ibm.com, felfert@millenux.com) - Peter Tiedemann (ptiedem@de.ibm.com) - * - * - * 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 - * - * 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. - * - */ - -#ifndef _CTCMAIN_H_ -#define _CTCMAIN_H_ - -#include <asm/ccwdev.h> -#include <asm/ccwgroup.h> - -#include <linux/skbuff.h> -#include <linux/netdevice.h> - -#include "fsm.h" -#include "cu3088.h" - - -/** - * 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_OS390 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 - -#define CTC_ID_SIZE BUS_ID_SIZE+3 - - -struct ctc_profile { - unsigned long maxmulti; - unsigned long maxcqueue; - unsigned long doios_single; - unsigned long doios_multi; - unsigned long txlen; - unsigned long tx_time; - struct timespec send_stamp; -}; - -/** - * Definition of one channel - */ -struct channel { - - /** - * Pointer to next channel in list. - */ - struct channel *next; - char id[CTC_ID_SIZE]; - struct ccw_device *cdev; - - /** - * Type of this channel. - * CTC/A or Escon for valid channels. - */ - enum channel_types type; - - /** - * Misc. flags. See CHANNEL_FLAGS_... below - */ - __u32 flags; - - /** - * The protocol of this channel - */ - __u16 protocol; - - /** - * I/O and irq related stuff - */ - struct ccw1 *ccw; - struct irb *irb; - - /** - * 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. - */ - struct net_device *netdev; - - struct ctc_profile prof; - - unsigned char *trans_skb_data; - - __u16 logflags; -}; - -#define CHANNEL_FLAGS_READ 0 -#define CHANNEL_FLAGS_WRITE 1 -#define CHANNEL_FLAGS_INUSE 2 -#define CHANNEL_FLAGS_BUFSIZE_CHANGED 4 -#define CHANNEL_FLAGS_FAILED 8 -#define CHANNEL_FLAGS_WAITIRQ 16 -#define CHANNEL_FLAGS_RWMASK 1 -#define CHANNEL_DIRECTION(f) (f & CHANNEL_FLAGS_RWMASK) - -#define LOG_FLAG_ILLEGALPKT 1 -#define LOG_FLAG_ILLEGALSIZE 2 -#define LOG_FLAG_OVERRUN 4 -#define LOG_FLAG_NOMEM 8 - -#define CTC_LOGLEVEL_INFO 1 -#define CTC_LOGLEVEL_NOTICE 2 -#define CTC_LOGLEVEL_WARN 4 -#define CTC_LOGLEVEL_EMERG 8 -#define CTC_LOGLEVEL_ERR 16 -#define CTC_LOGLEVEL_DEBUG 32 -#define CTC_LOGLEVEL_CRIT 64 - -#define CTC_LOGLEVEL_DEFAULT \ -(CTC_LOGLEVEL_INFO | CTC_LOGLEVEL_NOTICE | CTC_LOGLEVEL_WARN | CTC_LOGLEVEL_CRIT) - -#define CTC_LOGLEVEL_MAX ((CTC_LOGLEVEL_CRIT<<1)-1) - -#define ctc_pr_debug(fmt, arg...) \ -do { if (loglevel & CTC_LOGLEVEL_DEBUG) printk(KERN_DEBUG fmt,##arg); } while (0) - -#define ctc_pr_info(fmt, arg...) \ -do { if (loglevel & CTC_LOGLEVEL_INFO) printk(KERN_INFO fmt,##arg); } while (0) - -#define ctc_pr_notice(fmt, arg...) \ -do { if (loglevel & CTC_LOGLEVEL_NOTICE) printk(KERN_NOTICE fmt,##arg); } while (0) - -#define ctc_pr_warn(fmt, arg...) \ -do { if (loglevel & CTC_LOGLEVEL_WARN) printk(KERN_WARNING fmt,##arg); } while (0) - -#define ctc_pr_emerg(fmt, arg...) \ -do { if (loglevel & CTC_LOGLEVEL_EMERG) printk(KERN_EMERG fmt,##arg); } while (0) - -#define ctc_pr_err(fmt, arg...) \ -do { if (loglevel & CTC_LOGLEVEL_ERR) printk(KERN_ERR fmt,##arg); } while (0) - -#define ctc_pr_crit(fmt, arg...) \ -do { if (loglevel & CTC_LOGLEVEL_CRIT) printk(KERN_CRIT fmt,##arg); } while (0) - -struct ctc_priv { - struct net_device_stats stats; - unsigned long tbusy; - /** - * The finite state machine of this interface. - */ - fsm_instance *fsm; - /** - * The protocol of this device - */ - __u16 protocol; - /** - * Timer for restarting after I/O Errors - */ - fsm_timer restart_timer; - - int buffer_size; - - struct channel *channel[2]; -}; - -/** - * Definition of our link level header. - */ -struct ll_header { - __u16 length; - __u16 type; - __u16 unused; -}; -#define LL_HEADER_LENGTH (sizeof(struct ll_header)) - -/** - * Compatibility macros for busy handling - * of network devices. - */ -static __inline__ void -ctc_clear_busy(struct net_device * dev) -{ - clear_bit(0, &(((struct ctc_priv *) dev->priv)->tbusy)); - netif_wake_queue(dev); -} - -static __inline__ int -ctc_test_and_set_busy(struct net_device * dev) -{ - netif_stop_queue(dev); - return test_and_set_bit(0, &((struct ctc_priv *) dev->priv)->tbusy); -} - -#endif -- ^ permalink raw reply [flat|nested] 8+ messages in thread
* [patch 0/4] s390: ctc patches for 2.6.25 (3rd try) @ 2008-02-06 18:27 Ursula Braun 2008-02-06 18:27 ` [patch 3/4] ctcm: infrastructure for replaced ctc driver Ursula Braun 0 siblings, 1 reply; 8+ messages in thread From: Ursula Braun @ 2008-02-06 18:27 UTC (permalink / raw) To: jgarzik, netdev, linux-s390 -- Jeff, hmmm, another send is necessary, sorry again. Description for patch 3/4 needs to be changed. The following patches are intended for 2.6.25. Besides clean-ups they replace the old ctc driver by a reworked ctcm driver. This ctcm driver supports the channel-to-channel connections of the old ctc driver plus an additional MPC protocol to provide SNA connectivity. Patch 1/4: clean-ups in ctc and netiucv Patch 2/4: clean-ups in Kconfig Patch 3/4: reworked ctc driver Patch 4/4: removal of old ctc driver Regards, Ursula Braun ^ permalink raw reply [flat|nested] 8+ messages in thread
* [patch 3/4] ctcm: infrastructure for replaced ctc driver 2008-02-06 18:27 [patch 0/4] s390: ctc patches for 2.6.25 (3rd try) Ursula Braun @ 2008-02-06 18:27 ` Ursula Braun 2008-02-07 6:35 ` Christoph Hellwig 0 siblings, 1 reply; 8+ messages in thread From: Ursula Braun @ 2008-02-06 18:27 UTC (permalink / raw) To: jgarzik, netdev, linux-s390; +Cc: Peter Tiedemann [-- Attachment #1: 702-ctcm-diff --] [-- Type: text/plain, Size: 235442 bytes --] From: Peter Tiedemann <ptiedem@de.ibm.com> ctcm driver supports the channel-to-channel connections of the old ctc driver plus an additional MPC protocol to provide SNA connectivity. This new ctcm driver replaces the existing ctc driver. Signed-off-by: Peter Tiedemann <ptiedem@de.ibm.com> Signed-off-by: Ursula Braun <braunu@de.ibm.com> --- drivers/s390/net/Kconfig | 12 drivers/s390/net/Makefile | 5 drivers/s390/net/ctcm_dbug.c | 67 + drivers/s390/net/ctcm_dbug.h | 158 ++ drivers/s390/net/ctcm_fsms.c | 2338 +++++++++++++++++++++++++++++++++++++++ drivers/s390/net/ctcm_fsms.h | 359 ++++++ drivers/s390/net/ctcm_main.c | 1772 ++++++++++++++++++++++++++++++ drivers/s390/net/ctcm_main.h | 287 ++++ drivers/s390/net/ctcm_mpc.c | 2467 ++++++++++++++++++++++++++++++++++++++++++ drivers/s390/net/ctcm_mpc.h | 239 ++++ drivers/s390/net/ctcm_sysfs.c | 210 +++ 11 files changed, 7906 insertions(+), 8 deletions(-) Index: linux-2.6-uschi/drivers/s390/net/Makefile =================================================================== --- linux-2.6-uschi.orig/drivers/s390/net/Makefile +++ linux-2.6-uschi/drivers/s390/net/Makefile @@ -2,11 +2,10 @@ # S/390 network devices # -ctc-objs := ctcmain.o ctcdbug.o - +ctcm-objs := ctcm_main.o ctcm_fsms.o ctcm_mpc.o ctcm_sysfs.o ctcm_dbug.o +obj-$(CONFIG_CTCM) += ctcm.o fsm.o cu3088.o obj-$(CONFIG_NETIUCV) += netiucv.o fsm.o obj-$(CONFIG_SMSGIUCV) += smsgiucv.o -obj-$(CONFIG_CTC) += ctc.o fsm.o cu3088.o obj-$(CONFIG_LCS) += lcs.o cu3088.o obj-$(CONFIG_CLAW) += claw.o cu3088.o qeth-y := qeth_main.o qeth_mpc.o qeth_sys.o qeth_eddp.o Index: linux-2.6-uschi/drivers/s390/net/Kconfig =================================================================== --- linux-2.6-uschi.orig/drivers/s390/net/Kconfig +++ linux-2.6-uschi/drivers/s390/net/Kconfig @@ -11,15 +11,17 @@ config LCS To compile as a module, choose M. The module name is lcs.ko. If you do not know what it is, it's safe to choose Y. -config CTC - tristate "CTC device support" +config CTCM + tristate "CTC and MPC SNA device support" depends on CCW && NETDEVICES help Select this option if you want to use channel-to-channel point-to-point networking on IBM System z. This device driver supports real CTC coupling using ESCON. It also supports virtual CTCs when running under VM. - To compile as a module, choose M. The module name is ctc.ko. + This driver also supports channel-to-channel MPC SNA devices. + MPC is an SNA protocol device used by Communication Server for Linux. + To compile as a module, choose M. The module name is ctcm.ko. To compile into the kernel, choose Y. If you do not need any channel-to-channel connection, choose N. @@ -84,7 +86,7 @@ config QETH_VLAN 802.1q VLAN support in the qeth device driver. config CCWGROUP - tristate - default (LCS || CTC || QETH) + tristate + default (LCS || CTCM || QETH) endmenu Index: linux-2.6-uschi/drivers/s390/net/ctcm_dbug.c =================================================================== --- /dev/null +++ linux-2.6-uschi/drivers/s390/net/ctcm_dbug.c @@ -0,0 +1,67 @@ +/* + * drivers/s390/net/ctcm_dbug.c + * + * Copyright IBM Corp. 2001, 2007 + * Authors: Peter Tiedemann (ptiedem@de.ibm.com) + * + */ + +#include <linux/stddef.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/slab.h> +#include <linux/ctype.h> +#include <linux/sysctl.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/fs.h> +#include <linux/debugfs.h> +#include "ctcm_dbug.h" + +/* + * Debug Facility Stuff + */ + +DEFINE_PER_CPU(char[256], ctcm_dbf_txt_buf); + +struct ctcm_dbf_info ctcm_dbf[CTCM_DBF_INFOS] = { + [CTCM_DBF_SETUP] = {"ctc_setup", 8, 1, 64, 5, NULL}, + [CTCM_DBF_ERROR] = {"ctc_error", 8, 1, 64, 3, NULL}, + [CTCM_DBF_TRACE] = {"ctc_trace", 8, 1, 64, 3, NULL}, + [CTCM_DBF_MPC_SETUP] = {"mpc_setup", 8, 1, 64, 5, NULL}, + [CTCM_DBF_MPC_ERROR] = {"mpc_error", 8, 1, 64, 3, NULL}, + [CTCM_DBF_MPC_TRACE] = {"mpc_trace", 8, 1, 64, 3, NULL}, +}; + +void ctcm_unregister_dbf_views(void) +{ + int x; + for (x = 0; x < CTCM_DBF_INFOS; x++) { + debug_unregister(ctcm_dbf[x].id); + ctcm_dbf[x].id = NULL; + } +} + +int ctcm_register_dbf_views(void) +{ + int x; + for (x = 0; x < CTCM_DBF_INFOS; x++) { + /* register the areas */ + ctcm_dbf[x].id = debug_register(ctcm_dbf[x].name, + ctcm_dbf[x].pages, + ctcm_dbf[x].areas, + ctcm_dbf[x].len); + if (ctcm_dbf[x].id == NULL) { + ctcm_unregister_dbf_views(); + return -ENOMEM; + } + + /* register a view */ + debug_register_view(ctcm_dbf[x].id, &debug_hex_ascii_view); + /* set a passing level */ + debug_set_level(ctcm_dbf[x].id, ctcm_dbf[x].level); + } + + return 0; +} + Index: linux-2.6-uschi/drivers/s390/net/ctcm_dbug.h =================================================================== --- /dev/null +++ linux-2.6-uschi/drivers/s390/net/ctcm_dbug.h @@ -0,0 +1,158 @@ +/* + * drivers/s390/net/ctcm_dbug.h + * + * Copyright IBM Corp. 2001, 2007 + * Authors: Peter Tiedemann (ptiedem@de.ibm.com) + * + */ + +#ifndef _CTCM_DBUG_H_ +#define _CTCM_DBUG_H_ + +/* + * Debug Facility stuff + */ + +#include <asm/debug.h> + +#ifdef DEBUG + #define do_debug 1 +#else + #define do_debug 0 +#endif +#ifdef DEBUGDATA + #define do_debug_data 1 +#else + #define do_debug_data 0 +#endif +#ifdef DEBUGCCW + #define do_debug_ccw 1 +#else + #define do_debug_ccw 0 +#endif + +/* define dbf debug levels similar to kernel msg levels */ +#define CTC_DBF_ALWAYS 0 /* always print this */ +#define CTC_DBF_EMERG 0 /* system is unusable */ +#define CTC_DBF_ALERT 1 /* action must be taken immediately */ +#define CTC_DBF_CRIT 2 /* critical conditions */ +#define CTC_DBF_ERROR 3 /* error conditions */ +#define CTC_DBF_WARN 4 /* warning conditions */ +#define CTC_DBF_NOTICE 5 /* normal but significant condition */ +#define CTC_DBF_INFO 5 /* informational */ +#define CTC_DBF_DEBUG 6 /* debug-level messages */ + +DECLARE_PER_CPU(char[256], ctcm_dbf_txt_buf); + +enum ctcm_dbf_names { + CTCM_DBF_SETUP, + CTCM_DBF_ERROR, + CTCM_DBF_TRACE, + CTCM_DBF_MPC_SETUP, + CTCM_DBF_MPC_ERROR, + CTCM_DBF_MPC_TRACE, + CTCM_DBF_INFOS /* must be last element */ +}; + +struct ctcm_dbf_info { + char name[DEBUG_MAX_NAME_LEN]; + int pages; + int areas; + int len; + int level; + debug_info_t *id; +}; + +extern struct ctcm_dbf_info ctcm_dbf[CTCM_DBF_INFOS]; + +int ctcm_register_dbf_views(void); +void ctcm_unregister_dbf_views(void); + +static inline const char *strtail(const char *s, int n) +{ + int l = strlen(s); + return (l > n) ? s + (l - n) : s; +} + +/* sort out levels early to avoid unnecessary sprintfs */ +static inline int ctcm_dbf_passes(debug_info_t *dbf_grp, int level) +{ + return (dbf_grp->level >= level); +} + +#define CTCM_FUNTAIL strtail((char *)__func__, 16) + +#define CTCM_DBF_TEXT(name, level, text) \ + do { \ + debug_text_event(ctcm_dbf[CTCM_DBF_##name].id, level, text); \ + } while (0) + +#define CTCM_DBF_HEX(name, level, addr, len) \ + do { \ + debug_event(ctcm_dbf[CTCM_DBF_##name].id, \ + level, (void *)(addr), len); \ + } while (0) + +#define CTCM_DBF_TEXT_(name, level, text...) \ + do { \ + if (ctcm_dbf_passes(ctcm_dbf[CTCM_DBF_##name].id, level)) { \ + char *ctcm_dbf_txt_buf = \ + get_cpu_var(ctcm_dbf_txt_buf); \ + sprintf(ctcm_dbf_txt_buf, text); \ + debug_text_event(ctcm_dbf[CTCM_DBF_##name].id, \ + level, ctcm_dbf_txt_buf); \ + put_cpu_var(ctcm_dbf_txt_buf); \ + } \ + } while (0) + +/* + * cat : one of {setup, mpc_setup, trace, mpc_trace, error, mpc_error}. + * dev : netdevice with valid name field. + * text: any text string. + */ +#define CTCM_DBF_DEV_NAME(cat, dev, text) \ + do { \ + CTCM_DBF_TEXT_(cat, CTC_DBF_INFO, "%s(%s) : %s", \ + CTCM_FUNTAIL, dev->name, text); \ + } while (0) + +#define MPC_DBF_DEV_NAME(cat, dev, text) \ + do { \ + CTCM_DBF_TEXT_(MPC_##cat, CTC_DBF_INFO, "%s(%s) : %s", \ + CTCM_FUNTAIL, dev->name, text); \ + } while (0) + +#define CTCMY_DBF_DEV_NAME(cat, dev, text) \ + do { \ + if (IS_MPCDEV(dev)) \ + MPC_DBF_DEV_NAME(cat, dev, text); \ + else \ + CTCM_DBF_DEV_NAME(cat, dev, text); \ + } while (0) + +/* + * cat : one of {setup, mpc_setup, trace, mpc_trace, error, mpc_error}. + * dev : netdevice. + * text: any text string. + */ +#define CTCM_DBF_DEV(cat, dev, text) \ + do { \ + CTCM_DBF_TEXT_(cat, CTC_DBF_INFO, "%s(%p) : %s", \ + CTCM_FUNTAIL, dev, text); \ + } while (0) + +#define MPC_DBF_DEV(cat, dev, text) \ + do { \ + CTCM_DBF_TEXT_(MPC_##cat, CTC_DBF_INFO, "%s(%p) : %s", \ + CTCM_FUNTAIL, dev, text); \ + } while (0) + +#define CTCMY_DBF_DEV(cat, dev, text) \ + do { \ + if (IS_MPCDEV(dev)) \ + MPC_DBF_DEV(cat, dev, text); \ + else \ + CTCM_DBF_DEV(cat, dev, text); \ + } while (0) + +#endif Index: linux-2.6-uschi/drivers/s390/net/ctcm_fsms.c =================================================================== --- /dev/null +++ linux-2.6-uschi/drivers/s390/net/ctcm_fsms.c @@ -0,0 +1,2338 @@ +/* + * drivers/s390/net/ctcm_fsms.c + * + * Copyright IBM Corp. 2001, 2007 + * Authors: Fritz Elfert (felfert@millenux.com) + * Peter Tiedemann (ptiedem@de.ibm.com) + * MPC additions : + * Belinda Thompson (belindat@us.ibm.com) + * Andy Richter (richtera@us.ibm.com) + */ + +#undef DEBUG +#undef DEBUGDATA +#undef DEBUGCCW + +#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/bitops.h> + +#include <linux/signal.h> +#include <linux/string.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 <linux/io.h> +#include <asm/ccwdev.h> +#include <asm/ccwgroup.h> +#include <linux/uaccess.h> + +#include <asm/idals.h> + +#include "fsm.h" +#include "cu3088.h" + +#include "ctcm_dbug.h" +#include "ctcm_main.h" +#include "ctcm_fsms.h" + +const char *dev_state_names[] = { + [DEV_STATE_STOPPED] = "Stopped", + [DEV_STATE_STARTWAIT_RXTX] = "StartWait RXTX", + [DEV_STATE_STARTWAIT_RX] = "StartWait RX", + [DEV_STATE_STARTWAIT_TX] = "StartWait TX", + [DEV_STATE_STOPWAIT_RXTX] = "StopWait RXTX", + [DEV_STATE_STOPWAIT_RX] = "StopWait RX", + [DEV_STATE_STOPWAIT_TX] = "StopWait TX", + [DEV_STATE_RUNNING] = "Running", +}; + +const char *dev_event_names[] = { + [DEV_EVENT_START] = "Start", + [DEV_EVENT_STOP] = "Stop", + [DEV_EVENT_RXUP] = "RX up", + [DEV_EVENT_TXUP] = "TX up", + [DEV_EVENT_RXDOWN] = "RX down", + [DEV_EVENT_TXDOWN] = "TX down", + [DEV_EVENT_RESTART] = "Restart", +}; + +const char *ctc_ch_event_names[] = { + [CTC_EVENT_IO_SUCCESS] = "ccw_device success", + [CTC_EVENT_IO_EBUSY] = "ccw_device busy", + [CTC_EVENT_IO_ENODEV] = "ccw_device enodev", + [CTC_EVENT_IO_UNKNOWN] = "ccw_device unknown", + [CTC_EVENT_ATTNBUSY] = "Status ATTN & BUSY", + [CTC_EVENT_ATTN] = "Status ATTN", + [CTC_EVENT_BUSY] = "Status BUSY", + [CTC_EVENT_UC_RCRESET] = "Unit check remote reset", + [CTC_EVENT_UC_RSRESET] = "Unit check remote system reset", + [CTC_EVENT_UC_TXTIMEOUT] = "Unit check TX timeout", + [CTC_EVENT_UC_TXPARITY] = "Unit check TX parity", + [CTC_EVENT_UC_HWFAIL] = "Unit check Hardware failure", + [CTC_EVENT_UC_RXPARITY] = "Unit check RX parity", + [CTC_EVENT_UC_ZERO] = "Unit check ZERO", + [CTC_EVENT_UC_UNKNOWN] = "Unit check Unknown", + [CTC_EVENT_SC_UNKNOWN] = "SubChannel check Unknown", + [CTC_EVENT_MC_FAIL] = "Machine check failure", + [CTC_EVENT_MC_GOOD] = "Machine check operational", + [CTC_EVENT_IRQ] = "IRQ normal", + [CTC_EVENT_FINSTAT] = "IRQ final", + [CTC_EVENT_TIMER] = "Timer", + [CTC_EVENT_START] = "Start", + [CTC_EVENT_STOP] = "Stop", + /* + * additional MPC events + */ + [CTC_EVENT_SEND_XID] = "XID Exchange", + [CTC_EVENT_RSWEEP_TIMER] = "MPC Group Sweep Timer", +}; + +const char *ctc_ch_state_names[] = { + [CTC_STATE_IDLE] = "Idle", + [CTC_STATE_STOPPED] = "Stopped", + [CTC_STATE_STARTWAIT] = "StartWait", + [CTC_STATE_STARTRETRY] = "StartRetry", + [CTC_STATE_SETUPWAIT] = "SetupWait", + [CTC_STATE_RXINIT] = "RX init", + [CTC_STATE_TXINIT] = "TX init", + [CTC_STATE_RX] = "RX", + [CTC_STATE_TX] = "TX", + [CTC_STATE_RXIDLE] = "RX idle", + [CTC_STATE_TXIDLE] = "TX idle", + [CTC_STATE_RXERR] = "RX error", + [CTC_STATE_TXERR] = "TX error", + [CTC_STATE_TERM] = "Terminating", + [CTC_STATE_DTERM] = "Restarting", + [CTC_STATE_NOTOP] = "Not operational", + /* + * additional MPC states + */ + [CH_XID0_PENDING] = "Pending XID0 Start", + [CH_XID0_INPROGRESS] = "In XID0 Negotiations ", + [CH_XID7_PENDING] = "Pending XID7 P1 Start", + [CH_XID7_PENDING1] = "Active XID7 P1 Exchange ", + [CH_XID7_PENDING2] = "Pending XID7 P2 Start ", + [CH_XID7_PENDING3] = "Active XID7 P2 Exchange ", + [CH_XID7_PENDING4] = "XID7 Complete - Pending READY ", +}; + +/* + * ----- static ctcm actions for channel statemachine ----- + * +*/ +static void chx_txdone(fsm_instance *fi, int event, void *arg); +static void chx_rx(fsm_instance *fi, int event, void *arg); +static void chx_rxidle(fsm_instance *fi, int event, void *arg); +static void chx_firstio(fsm_instance *fi, int event, void *arg); +static void ctcm_chx_setmode(fsm_instance *fi, int event, void *arg); +static void ctcm_chx_start(fsm_instance *fi, int event, void *arg); +static void ctcm_chx_haltio(fsm_instance *fi, int event, void *arg); +static void ctcm_chx_stopped(fsm_instance *fi, int event, void *arg); +static void ctcm_chx_stop(fsm_instance *fi, int event, void *arg); +static void ctcm_chx_fail(fsm_instance *fi, int event, void *arg); +static void ctcm_chx_setuperr(fsm_instance *fi, int event, void *arg); +static void ctcm_chx_restart(fsm_instance *fi, int event, void *arg); +static void ctcm_chx_rxiniterr(fsm_instance *fi, int event, void *arg); +static void ctcm_chx_rxinitfail(fsm_instance *fi, int event, void *arg); +static void ctcm_chx_rxdisc(fsm_instance *fi, int event, void *arg); +static void ctcm_chx_txiniterr(fsm_instance *fi, int event, void *arg); +static void ctcm_chx_txretry(fsm_instance *fi, int event, void *arg); +static void ctcm_chx_iofatal(fsm_instance *fi, int event, void *arg); + +/* + * ----- static ctcmpc actions for ctcmpc channel statemachine ----- + * +*/ +static void ctcmpc_chx_txdone(fsm_instance *fi, int event, void *arg); +static void ctcmpc_chx_rx(fsm_instance *fi, int event, void *arg); +static void ctcmpc_chx_firstio(fsm_instance *fi, int event, void *arg); +/* shared : +static void ctcm_chx_setmode(fsm_instance *fi, int event, void *arg); +static void ctcm_chx_start(fsm_instance *fi, int event, void *arg); +static void ctcm_chx_haltio(fsm_instance *fi, int event, void *arg); +static void ctcm_chx_stopped(fsm_instance *fi, int event, void *arg); +static void ctcm_chx_stop(fsm_instance *fi, int event, void *arg); +static void ctcm_chx_fail(fsm_instance *fi, int event, void *arg); +static void ctcm_chx_setuperr(fsm_instance *fi, int event, void *arg); +static void ctcm_chx_restart(fsm_instance *fi, int event, void *arg); +static void ctcm_chx_rxiniterr(fsm_instance *fi, int event, void *arg); +static void ctcm_chx_rxinitfail(fsm_instance *fi, int event, void *arg); +static void ctcm_chx_rxdisc(fsm_instance *fi, int event, void *arg); +static void ctcm_chx_txiniterr(fsm_instance *fi, int event, void *arg); +static void ctcm_chx_txretry(fsm_instance *fi, int event, void *arg); +static void ctcm_chx_iofatal(fsm_instance *fi, int event, void *arg); +*/ +static void ctcmpc_chx_attn(fsm_instance *fsm, int event, void *arg); +static void ctcmpc_chx_attnbusy(fsm_instance *, int, void *); +static void ctcmpc_chx_resend(fsm_instance *, int, void *); +static void ctcmpc_chx_send_sweep(fsm_instance *fsm, int event, void *arg); + +/** + * Check return code of a preceeding ccw_device call, halt_IO etc... + * + * @param ch The channel, the error belongs to. + * @param return_code The error code (!= 0) to inspect. + */ +void ctcm_ccw_check_rc(struct channel *ch, int rc, char *msg) +{ + CTCM_DBF_TEXT_(ERROR, CTC_DBF_ERROR, + "ccw error %s (%s): %04x\n", ch->id, msg, rc); + switch (rc) { + case -EBUSY: + ctcm_pr_warn("%s (%s): Busy !\n", ch->id, msg); + fsm_event(ch->fsm, CTC_EVENT_IO_EBUSY, ch); + break; + case -ENODEV: + ctcm_pr_emerg("%s (%s): Invalid device called for IO\n", + ch->id, msg); + fsm_event(ch->fsm, CTC_EVENT_IO_ENODEV, ch); + break; + default: + ctcm_pr_emerg("%s (%s): Unknown error in do_IO %04x\n", + ch->id, msg, rc); + fsm_event(ch->fsm, CTC_EVENT_IO_UNKNOWN, ch); + } +} + +void ctcm_purge_skb_queue(struct sk_buff_head *q) +{ + struct sk_buff *skb; + + CTCM_DBF_TEXT(TRACE, 3, __FUNCTION__); + + while ((skb = skb_dequeue(q))) { + atomic_dec(&skb->users); + dev_kfree_skb_any(skb); + } +} + +/* + * 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 chx_txdone(fsm_instance *fi, int event, void *arg) +{ + struct channel *ch = (struct channel *)arg; + struct net_device *dev = ch->netdev; + struct ctcm_priv *privptr = dev->priv; + struct sk_buff *skb; + int first = 1; + int i; + unsigned long duration; + struct timespec done_stamp = current_kernel_time(); /* xtime */ + + duration = + (done_stamp.tv_sec - ch->prof.send_stamp.tv_sec) * 1000000 + + (done_stamp.tv_nsec - ch->prof.send_stamp.tv_nsec) / 1000; + if (duration > ch->prof.tx_time) + ch->prof.tx_time = duration; + + if (ch->irb->scsw.count != 0) + ctcm_pr_debug("%s: TX not complete, remaining %d bytes\n", + dev->name, ch->irb->scsw.count); + 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 (ctcm_checkalloc_buffer(ch)) { + spin_unlock(&ch->collect_lock); + return; + } + ch->trans_skb->data = ch->trans_skb_data; + skb_reset_tail_pointer(ch->trans_skb); + 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); + *((__u16 *)skb_put(ch->trans_skb, 2)) = ch->collect_len + 2; + i = 0; + while ((skb = skb_dequeue(&ch->collect_queue))) { + skb_copy_from_linear_data(skb, + skb_put(ch->trans_skb, skb->len), 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; + spin_unlock(&ch->collect_lock); + ch->ccw[1].count = ch->trans_skb->len; + fsm_addtimer(&ch->timer, CTCM_TIME_5_SEC, CTC_EVENT_TIMER, ch); + ch->prof.send_stamp = current_kernel_time(); /* xtime */ + rc = ccw_device_start(ch->cdev, &ch->ccw[0], + (unsigned long)ch, 0xff, 0); + ch->prof.doios_multi++; + if (rc != 0) { + privptr->stats.tx_dropped += i; + privptr->stats.tx_errors += i; + fsm_deltimer(&ch->timer); + ctcm_ccw_check_rc(ch, rc, "chained TX"); + } + } else { + spin_unlock(&ch->collect_lock); + fsm_newstate(fi, CTC_STATE_TXIDLE); + } + ctcm_clear_busy_do(dev); +} + +/** + * 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. + */ +void ctcm_chx_txidle(fsm_instance *fi, int event, void *arg) +{ + struct channel *ch = (struct channel *)arg; + + CTCM_DBF_TEXT(TRACE, 6, __FUNCTION__); + fsm_deltimer(&ch->timer); + fsm_newstate(fi, CTC_STATE_TXIDLE); + fsm_event(((struct ctcm_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 chx_rx(fsm_instance *fi, int event, void *arg) +{ + struct channel *ch = (struct channel *)arg; + struct net_device *dev = ch->netdev; + struct ctcm_priv *privptr = dev->priv; + int len = ch->max_bufsize - ch->irb->scsw.count; + 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) { + ctcm_pr_debug("%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) { + ctcm_pr_debug("%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 CTCM_PROTO_S390: + case CTCM_PROTO_OS390: + check_len = block_len + 2; + break; + default: + check_len = block_len; + break; + } + if ((len < block_len) || (len > check_len)) { + ctcm_pr_debug("%s: got block length %d != rx length %d\n", + dev->name, block_len, len); + if (do_debug) + ctcmpc_dump_skb(skb, 0); + + *((__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; + ctcm_unpack_skb(ch, skb); + } + again: + skb->data = ch->trans_skb_data; + skb_reset_tail_pointer(skb); + skb->len = 0; + if (ctcm_checkalloc_buffer(ch)) + return; + ch->ccw[1].count = ch->max_bufsize; + rc = ccw_device_start(ch->cdev, &ch->ccw[0], + (unsigned long)ch, 0xff, 0); + if (rc != 0) + ctcm_ccw_check_rc(ch, rc, "normal RX"); +} + +static void chx_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 chx_firstio(fsm_instance *fi, int event, void *arg) +{ + struct channel *ch = (struct channel *)arg; + int rc; + + CTCM_DBF_TEXT(TRACE, 6, __FUNCTION__); + + if (fsm_getstate(fi) == CTC_STATE_TXIDLE) + ctcm_pr_debug("%s: remote side issued READ?, init.\n", ch->id); + fsm_deltimer(&ch->timer); + if (ctcm_checkalloc_buffer(ch)) + return; + if ((fsm_getstate(fi) == CTC_STATE_SETUPWAIT) && + (ch->protocol == CTCM_PROTO_OS390)) { + /* OS/390 resp. z/OS */ + if (CHANNEL_DIRECTION(ch->flags) == READ) { + *((__u16 *)ch->trans_skb->data) = CTCM_INITIAL_BLOCKLEN; + fsm_addtimer(&ch->timer, CTCM_TIME_5_SEC, + CTC_EVENT_TIMER, ch); + chx_rxidle(fi, event, arg); + } else { + struct net_device *dev = ch->netdev; + fsm_newstate(fi, CTC_STATE_TXIDLE); + fsm_event(((struct ctcm_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 != CTCM_PROTO_S390)) + fsm_addtimer(&ch->timer, CTCM_TIME_5_SEC, CTC_EVENT_TIMER, ch); + + *((__u16 *)ch->trans_skb->data) = CTCM_INITIAL_BLOCKLEN; + ch->ccw[1].count = 2; /* Transfer only length */ + + fsm_newstate(fi, (CHANNEL_DIRECTION(ch->flags) == READ) + ? CTC_STATE_RXINIT : CTC_STATE_TXINIT); + rc = ccw_device_start(ch->cdev, &ch->ccw[0], + (unsigned long)ch, 0xff, 0); + if (rc != 0) { + fsm_deltimer(&ch->timer); + fsm_newstate(fi, CTC_STATE_SETUPWAIT); + ctcm_ccw_check_rc(ch, rc, "init IO"); + } + /* + * 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 == CTCM_PROTO_S390)) { + struct net_device *dev = ch->netdev; + fsm_event(((struct ctcm_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 chx_rxidle(fsm_instance *fi, int event, void *arg) +{ + struct channel *ch = (struct channel *)arg; + struct net_device *dev = ch->netdev; + __u16 buflen; + int rc; + + CTCM_DBF_TEXT(TRACE, 6, __FUNCTION__); + fsm_deltimer(&ch->timer); + buflen = *((__u16 *)ch->trans_skb->data); + if (do_debug) + ctcm_pr_debug("%s: Initial RX count %d\n", dev->name, buflen); + + if (buflen >= CTCM_INITIAL_BLOCKLEN) { + if (ctcm_checkalloc_buffer(ch)) + return; + ch->ccw[1].count = ch->max_bufsize; + fsm_newstate(fi, CTC_STATE_RXIDLE); + rc = ccw_device_start(ch->cdev, &ch->ccw[0], + (unsigned long)ch, 0xff, 0); + if (rc != 0) { + fsm_newstate(fi, CTC_STATE_RXINIT); + ctcm_ccw_check_rc(ch, rc, "initial RX"); + } else + fsm_event(((struct ctcm_priv *)dev->priv)->fsm, + DEV_EVENT_RXUP, dev); + } else { + if (do_debug) + ctcm_pr_debug("%s: Initial RX count %d not %d\n", + dev->name, buflen, CTCM_INITIAL_BLOCKLEN); + chx_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 ctcm_chx_setmode(fsm_instance *fi, int event, void *arg) +{ + struct channel *ch = (struct channel *)arg; + int rc; + unsigned long saveflags = 0; + int timeout = CTCM_TIME_5_SEC; + + fsm_deltimer(&ch->timer); + if (IS_MPC(ch)) { + timeout = 1500; + if (do_debug) + ctcm_pr_debug("ctcm enter: %s(): cp=%i ch=0x%p id=%s\n", + __FUNCTION__, smp_processor_id(), ch, ch->id); + } + fsm_addtimer(&ch->timer, timeout, CTC_EVENT_TIMER, ch); + fsm_newstate(fi, CTC_STATE_SETUPWAIT); + if (do_debug_ccw && IS_MPC(ch)) + ctcmpc_dumpit((char *)&ch->ccw[6], sizeof(struct ccw1) * 2); + + if (event == CTC_EVENT_TIMER) /* only for timer not yet locked */ + spin_lock_irqsave(get_ccwdev_lock(ch->cdev), saveflags); + /* Such conditional locking is undeterministic in + * static view. => ignore sparse warnings here. */ + + rc = ccw_device_start(ch->cdev, &ch->ccw[6], + (unsigned long)ch, 0xff, 0); + if (event == CTC_EVENT_TIMER) /* see above comments */ + spin_unlock_irqrestore(get_ccwdev_lock(ch->cdev), saveflags); + if (rc != 0) { + fsm_deltimer(&ch->timer); + fsm_newstate(fi, CTC_STATE_STARTWAIT); + ctcm_ccw_check_rc(ch, rc, "set Mode"); + } 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 ctcm_chx_start(fsm_instance *fi, int event, void *arg) +{ + struct channel *ch = (struct channel *)arg; + int rc; + struct net_device *dev; + unsigned long saveflags; + + CTCM_DBF_TEXT(TRACE, 5, __FUNCTION__); + if (ch == NULL) { + ctcm_pr_warn("chx_start ch=NULL\n"); + return; + } + if (ch->netdev == NULL) { + ctcm_pr_warn("chx_start dev=NULL, id=%s\n", ch->id); + return; + } + dev = ch->netdev; + + if (do_debug) + ctcm_pr_debug("%s: %s channel start\n", dev->name, + (CHANNEL_DIRECTION(ch->flags) == READ) ? "RX" : "TX"); + + 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 (ctcm_checkalloc_buffer(ch)) { + ctcm_pr_notice("%s: %s trans_skb allocation delayed " + "until first transfer\n", dev->name, + (CHANNEL_DIRECTION(ch->flags) == READ) ? "RX" : "TX"); + } + + 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(struct ccw1) * 3); + ch->ccw[4].cda = 0; + ch->ccw[4].flags &= ~CCW_FLAG_IDA; + + fsm_newstate(fi, CTC_STATE_STARTWAIT); + fsm_addtimer(&ch->timer, 1000, CTC_EVENT_TIMER, ch); + spin_lock_irqsave(get_ccwdev_lock(ch->cdev), saveflags); + rc = ccw_device_halt(ch->cdev, (unsigned long)ch); + spin_unlock_irqrestore(get_ccwdev_lock(ch->cdev), saveflags); + if (rc != 0) { + if (rc != -EBUSY) + fsm_deltimer(&ch->timer); + ctcm_ccw_check_rc(ch, rc, "initial HaltIO"); + } +} + +/** + * 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 ctcm_chx_haltio(fsm_instance *fi, int event, void *arg) +{ + struct channel *ch = (struct channel *)arg; + unsigned long saveflags = 0; + int rc; + int oldstate; + + CTCM_DBF_TEXT(TRACE, 2, __FUNCTION__); + fsm_deltimer(&ch->timer); + if (IS_MPC(ch)) + fsm_deltimer(&ch->sweep_timer); + + fsm_addtimer(&ch->timer, CTCM_TIME_5_SEC, CTC_EVENT_TIMER, ch); + + if (event == CTC_EVENT_STOP) /* only for STOP not yet locked */ + spin_lock_irqsave(get_ccwdev_lock(ch->cdev), saveflags); + /* Such conditional locking is undeterministic in + * static view. => ignore sparse warnings here. */ + oldstate = fsm_getstate(fi); + fsm_newstate(fi, CTC_STATE_TERM); + rc = ccw_device_halt(ch->cdev, (unsigned long)ch); + + if (event == CTC_EVENT_STOP) + spin_unlock_irqrestore(get_ccwdev_lock(ch->cdev), saveflags); + /* see remark above about conditional locking */ + + if (rc != 0 && rc != -EBUSY) { + fsm_deltimer(&ch->timer); + if (event != CTC_EVENT_STOP) { + fsm_newstate(fi, oldstate); + ctcm_ccw_check_rc(ch, rc, (char *)__FUNCTION__); + } + } +} + +/** + * Cleanup helper for chx_fail and chx_stopped + * cleanup channels queue and notify interface statemachine. + * + * @param fi An instance of a channel statemachine. + * @param state The next state (depending on caller). + * @param ch The channel to operate on. + */ +static void ctcm_chx_cleanup(fsm_instance *fi, int state, + struct channel *ch) +{ + struct net_device *dev = ch->netdev; + + CTCM_DBF_TEXT(TRACE, 3, __FUNCTION__); + + fsm_deltimer(&ch->timer); + if (IS_MPC(ch)) + fsm_deltimer(&ch->sweep_timer); + + fsm_newstate(fi, state); + if (state == CTC_STATE_STOPPED && ch->trans_skb != NULL) { + clear_normalized_cda(&ch->ccw[1]); + dev_kfree_skb_any(ch->trans_skb); + ch->trans_skb = NULL; + } + + ch->th_seg = 0x00; + ch->th_seq_num = 0x00; + if (CHANNEL_DIRECTION(ch->flags) == READ) { + skb_queue_purge(&ch->io_queue); + fsm_event(((struct ctcm_priv *)dev->priv)->fsm, + DEV_EVENT_RXDOWN, dev); + } else { + ctcm_purge_skb_queue(&ch->io_queue); + if (IS_MPC(ch)) + ctcm_purge_skb_queue(&ch->sweep_queue); + spin_lock(&ch->collect_lock); + ctcm_purge_skb_queue(&ch->collect_queue); + ch->collect_len = 0; + spin_unlock(&ch->collect_lock); + fsm_event(((struct ctcm_priv *)dev->priv)->fsm, + DEV_EVENT_TXDOWN, dev); + } +} + +/** + * 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 ctcm_chx_stopped(fsm_instance *fi, int event, void *arg) +{ + CTCM_DBF_TEXT(TRACE, 3, __FUNCTION__); + ctcm_chx_cleanup(fi, CTC_STATE_STOPPED, (struct channel *)arg); +} + +/** + * 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 ctcm_chx_stop(fsm_instance *fi, int event, void *arg) +{ + fsm_newstate(fi, CTC_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 ctcm_chx_fail(fsm_instance *fi, int event, void *arg) +{ + CTCM_DBF_TEXT(TRACE, 3, __FUNCTION__); + ctcm_chx_cleanup(fi, CTC_STATE_NOTOP, (struct channel *)arg); +} + +/** + * 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 ctcm_chx_setuperr(fsm_instance *fi, int event, void *arg) +{ + struct channel *ch = (struct channel *)arg; + struct net_device *dev = ch->netdev; + struct ctcm_priv *priv = (struct ctcm_priv *)dev->priv; + + /* + * 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) == CTC_STATE_SETUPWAIT) && + ((event == CTC_EVENT_UC_RCRESET) || + (event == CTC_EVENT_UC_RSRESET))) { + fsm_newstate(fi, CTC_STATE_STARTRETRY); + fsm_deltimer(&ch->timer); + fsm_addtimer(&ch->timer, CTCM_TIME_5_SEC, CTC_EVENT_TIMER, ch); + if (!IS_MPC(ch) && (CHANNEL_DIRECTION(ch->flags) == READ)) { + int rc = ccw_device_halt(ch->cdev, (unsigned long)ch); + if (rc != 0) + ctcm_ccw_check_rc(ch, rc, + "HaltIO in chx_setuperr"); + } + return; + } + + CTCM_DBF_TEXT_(ERROR, CTC_DBF_CRIT, + "%s : %s error during %s channel setup state=%s\n", + dev->name, ctc_ch_event_names[event], + (CHANNEL_DIRECTION(ch->flags) == READ) ? "RX" : "TX", + fsm_getstate_str(fi)); + + if (CHANNEL_DIRECTION(ch->flags) == READ) { + fsm_newstate(fi, CTC_STATE_RXERR); + fsm_event(priv->fsm, DEV_EVENT_RXDOWN, dev); + } else { + fsm_newstate(fi, CTC_STATE_TXERR); + fsm_event(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 ctcm_chx_restart(fsm_instance *fi, int event, void *arg) +{ + struct channel *ch = (struct channel *)arg; + struct net_device *dev = ch->netdev; + unsigned long saveflags = 0; + int oldstate; + int rc; + + CTCM_DBF_TEXT(TRACE, CTC_DBF_NOTICE, __FUNCTION__); + fsm_deltimer(&ch->timer); + ctcm_pr_debug("%s: %s channel restart\n", dev->name, + (CHANNEL_DIRECTION(ch->flags) == READ) ? "RX" : "TX"); + fsm_addtimer(&ch->timer, CTCM_TIME_5_SEC, CTC_EVENT_TIMER, ch); + oldstate = fsm_getstate(fi); + fsm_newstate(fi, CTC_STATE_STARTWAIT); + if (event == CTC_EVENT_TIMER) /* only for timer not yet locked */ + spin_lock_irqsave(get_ccwdev_lock(ch->cdev), saveflags); + /* Such conditional locking is a known problem for + * sparse because its undeterministic in static view. + * Warnings should be ignored here. */ + rc = ccw_device_halt(ch->cdev, (unsigned long)ch); + if (event == CTC_EVENT_TIMER) + spin_unlock_irqrestore(get_ccwdev_lock(ch->cdev), saveflags); + if (rc != 0) { + if (rc != -EBUSY) { + fsm_deltimer(&ch->timer); + fsm_newstate(fi, oldstate); + } + ctcm_ccw_check_rc(ch, rc, "HaltIO in ctcm_chx_restart"); + } +} + +/** + * 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 ctcm_chx_rxiniterr(fsm_instance *fi, int event, void *arg) +{ + struct channel *ch = (struct channel *)arg; + struct net_device *dev = ch->netdev; + + CTCM_DBF_TEXT(SETUP, 3, __FUNCTION__); + if (event == CTC_EVENT_TIMER) { + if (!IS_MPCDEV(dev)) + /* TODO : check if MPC deletes timer somewhere */ + fsm_deltimer(&ch->timer); + ctcm_pr_debug("%s: Timeout during RX init handshake\n", + dev->name); + if (ch->retry++ < 3) + ctcm_chx_restart(fi, event, arg); + else { + fsm_newstate(fi, CTC_STATE_RXERR); + fsm_event(((struct ctcm_priv *)dev->priv)->fsm, + DEV_EVENT_RXDOWN, dev); + } + } else + ctcm_pr_warn("%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 ctcm_chx_rxinitfail(fsm_instance *fi, int event, void *arg) +{ + struct channel *ch = (struct channel *)arg; + struct net_device *dev = ch->netdev; + + CTCM_DBF_TEXT(SETUP, 3, __FUNCTION__); + fsm_newstate(fi, CTC_STATE_RXERR); + ctcm_pr_warn("%s: RX busy. Initialization failed\n", dev->name); + fsm_event(((struct ctcm_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 ctcm_chx_rxdisc(fsm_instance *fi, int event, void *arg) +{ + struct channel *ch = (struct channel *)arg; + struct channel *ch2; + struct net_device *dev = ch->netdev; + + CTCM_DBF_DEV_NAME(TRACE, dev, "Got remote disconnect, re-initializing"); + fsm_deltimer(&ch->timer); + if (do_debug) + ctcm_pr_debug("%s: Got remote disconnect, " + "re-initializing ...\n", dev->name); + /* + * Notify device statemachine + */ + fsm_event(((struct ctcm_priv *)dev->priv)->fsm, DEV_EVENT_RXDOWN, dev); + fsm_event(((struct ctcm_priv *)dev->priv)->fsm, DEV_EVENT_TXDOWN, dev); + + fsm_newstate(fi, CTC_STATE_DTERM); + ch2 = ((struct ctcm_priv *)dev->priv)->channel[WRITE]; + fsm_newstate(ch2->fsm, CTC_STATE_DTERM); + + ccw_device_halt(ch->cdev, (unsigned long)ch); + ccw_device_halt(ch2->cdev, (unsigned long)ch2); +} + +/** + * 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 ctcm_chx_txiniterr(fsm_instance *fi, int event, void *arg) +{ + struct channel *ch = (struct channel *)arg; + struct net_device *dev = ch->netdev; + + if (event == CTC_EVENT_TIMER) { + fsm_deltimer(&ch->timer); + CTCM_DBF_DEV_NAME(ERROR, dev, + "Timeout during TX init handshake"); + if (ch->retry++ < 3) + ctcm_chx_restart(fi, event, arg); + else { + fsm_newstate(fi, CTC_STATE_TXERR); + fsm_event(((struct ctcm_priv *)dev->priv)->fsm, + DEV_EVENT_TXDOWN, dev); + } + } else { + CTCM_DBF_TEXT_(ERROR, CTC_DBF_ERROR, + "%s : %s error during channel setup state=%s", + dev->name, ctc_ch_event_names[event], + fsm_getstate_str(fi)); + + ctcm_pr_warn("%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 ctcm_chx_txretry(fsm_instance *fi, int event, void *arg) +{ + struct channel *ch = (struct channel *)arg; + struct net_device *dev = ch->netdev; + struct ctcm_priv *priv = (struct ctcm_priv *)dev->priv; + struct sk_buff *skb; + + if (do_debug) + ctcm_pr_debug("ctcmpc enter: %s(): cp=%i ch=0x%p id=%s\n", + __FUNCTION__, smp_processor_id(), ch, ch->id); + + fsm_deltimer(&ch->timer); + if (ch->retry++ > 3) { + struct mpc_group *gptr = priv->mpcg; + ctcm_pr_debug("%s: TX retry failed, restarting channel\n", + dev->name); + fsm_event(priv->fsm, DEV_EVENT_TXDOWN, dev); + /* call restart if not MPC or if MPC and mpcg fsm is ready. + use gptr as mpc indicator */ + if (!(gptr && (fsm_getstate(gptr->fsm) != MPCG_STATE_READY))) + ctcm_chx_restart(fi, event, arg); + goto done; + } + + ctcm_pr_debug("%s: TX retry %d\n", dev->name, ch->retry); + skb = skb_peek(&ch->io_queue); + if (skb) { + int rc = 0; + unsigned long saveflags = 0; + clear_normalized_cda(&ch->ccw[4]); + ch->ccw[4].count = skb->len; + if (set_normalized_cda(&ch->ccw[4], skb->data)) { + ctcm_pr_debug("%s: IDAL alloc failed, chan restart\n", + dev->name); + fsm_event(priv->fsm, DEV_EVENT_TXDOWN, dev); + ctcm_chx_restart(fi, event, arg); + goto done; + } + fsm_addtimer(&ch->timer, 1000, CTC_EVENT_TIMER, ch); + if (event == CTC_EVENT_TIMER) /* for TIMER not yet locked */ + spin_lock_irqsave(get_ccwdev_lock(ch->cdev), saveflags); + /* Such conditional locking is a known problem for + * sparse because its undeterministic in static view. + * Warnings should be ignored here. */ + if (do_debug_ccw) + ctcmpc_dumpit((char *)&ch->ccw[3], + sizeof(struct ccw1) * 3); + + rc = ccw_device_start(ch->cdev, &ch->ccw[3], + (unsigned long)ch, 0xff, 0); + if (event == CTC_EVENT_TIMER) + spin_unlock_irqrestore(get_ccwdev_lock(ch->cdev), + saveflags); + if (rc != 0) { + fsm_deltimer(&ch->timer); + ctcm_ccw_check_rc(ch, rc, "TX in chx_txretry"); + ctcm_purge_skb_queue(&ch->io_queue); + } + } +done: + return; +} + +/** + * 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 ctcm_chx_iofatal(fsm_instance *fi, int event, void *arg) +{ + struct channel *ch = (struct channel *)arg; + struct net_device *dev = ch->netdev; + + CTCM_DBF_TEXT(TRACE, 3, __FUNCTION__); + fsm_deltimer(&ch->timer); + ctcm_pr_warn("%s %s : unrecoverable channel error\n", + CTC_DRIVER_NAME, dev->name); + if (IS_MPC(ch)) { + ((struct ctcm_priv *)dev->priv)->stats.tx_dropped++; + ((struct ctcm_priv *)dev->priv)->stats.tx_errors++; + } + + if (CHANNEL_DIRECTION(ch->flags) == READ) { + ctcm_pr_debug("%s: RX I/O error\n", dev->name); + fsm_newstate(fi, CTC_STATE_RXERR); + fsm_event(((struct ctcm_priv *)dev->priv)->fsm, + DEV_EVENT_RXDOWN, dev); + } else { + ctcm_pr_debug("%s: TX I/O error\n", dev->name); + fsm_newstate(fi, CTC_STATE_TXERR); + fsm_event(((struct ctcm_priv *)dev->priv)->fsm, + DEV_EVENT_TXDOWN, dev); + } +} + +/* + * The ctcm statemachine for a channel. + */ +const fsm_node ch_fsm[] = { + { CTC_STATE_STOPPED, CTC_EVENT_STOP, fsm_action_nop }, + { CTC_STATE_STOPPED, CTC_EVENT_START, ctcm_chx_start }, + { CTC_STATE_STOPPED, CTC_EVENT_FINSTAT, fsm_action_nop }, + { CTC_STATE_STOPPED, CTC_EVENT_MC_FAIL, fsm_action_nop }, + + { CTC_STATE_NOTOP, CTC_EVENT_STOP, ctcm_chx_stop }, + { CTC_STATE_NOTOP, CTC_EVENT_START, fsm_action_nop }, + { CTC_STATE_NOTOP, CTC_EVENT_FINSTAT, fsm_action_nop }, + { CTC_STATE_NOTOP, CTC_EVENT_MC_FAIL, fsm_action_nop }, + { CTC_STATE_NOTOP, CTC_EVENT_MC_GOOD, ctcm_chx_start }, + + { CTC_STATE_STARTWAIT, CTC_EVENT_STOP, ctcm_chx_haltio }, + { CTC_STATE_STARTWAIT, CTC_EVENT_START, fsm_action_nop }, + { CTC_STATE_STARTWAIT, CTC_EVENT_FINSTAT, ctcm_chx_setmode }, + { CTC_STATE_STARTWAIT, CTC_EVENT_TIMER, ctcm_chx_setuperr }, + { CTC_STATE_STARTWAIT, CTC_EVENT_IO_ENODEV, ctcm_chx_iofatal }, + { CTC_STATE_STARTWAIT, CTC_EVENT_MC_FAIL, ctcm_chx_fail }, + + { CTC_STATE_STARTRETRY, CTC_EVENT_STOP, ctcm_chx_haltio }, + { CTC_STATE_STARTRETRY, CTC_EVENT_TIMER, ctcm_chx_setmode }, + { CTC_STATE_STARTRETRY, CTC_EVENT_FINSTAT, fsm_action_nop }, + { CTC_STATE_STARTRETRY, CTC_EVENT_MC_FAIL, ctcm_chx_fail }, + + { CTC_STATE_SETUPWAIT, CTC_EVENT_STOP, ctcm_chx_haltio }, + { CTC_STATE_SETUPWAIT, CTC_EVENT_START, fsm_action_nop }, + { CTC_STATE_SETUPWAIT, CTC_EVENT_FINSTAT, chx_firstio }, + { CTC_STATE_SETUPWAIT, CTC_EVENT_UC_RCRESET, ctcm_chx_setuperr }, + { CTC_STATE_SETUPWAIT, CTC_EVENT_UC_RSRESET, ctcm_chx_setuperr }, + { CTC_STATE_SETUPWAIT, CTC_EVENT_TIMER, ctcm_chx_setmode }, + { CTC_STATE_SETUPWAIT, CTC_EVENT_IO_ENODEV, ctcm_chx_iofatal }, + { CTC_STATE_SETUPWAIT, CTC_EVENT_MC_FAIL, ctcm_chx_fail }, + + { CTC_STATE_RXINIT, CTC_EVENT_STOP, ctcm_chx_haltio }, + { CTC_STATE_RXINIT, CTC_EVENT_START, fsm_action_nop }, + { CTC_STATE_RXINIT, CTC_EVENT_FINSTAT, chx_rxidle }, + { CTC_STATE_RXINIT, CTC_EVENT_UC_RCRESET, ctcm_chx_rxiniterr }, + { CTC_STATE_RXINIT, CTC_EVENT_UC_RSRESET, ctcm_chx_rxiniterr }, + { CTC_STATE_RXINIT, CTC_EVENT_TIMER, ctcm_chx_rxiniterr }, + { CTC_STATE_RXINIT, CTC_EVENT_ATTNBUSY, ctcm_chx_rxinitfail }, + { CTC_STATE_RXINIT, CTC_EVENT_IO_ENODEV, ctcm_chx_iofatal }, + { CTC_STATE_RXINIT, CTC_EVENT_UC_ZERO, chx_firstio }, + { CTC_STATE_RXINIT, CTC_EVENT_MC_FAIL, ctcm_chx_fail }, + + { CTC_STATE_RXIDLE, CTC_EVENT_STOP, ctcm_chx_haltio }, + { CTC_STATE_RXIDLE, CTC_EVENT_START, fsm_action_nop }, + { CTC_STATE_RXIDLE, CTC_EVENT_FINSTAT, chx_rx }, + { CTC_STATE_RXIDLE, CTC_EVENT_UC_RCRESET, ctcm_chx_rxdisc }, + { CTC_STATE_RXIDLE, CTC_EVENT_IO_ENODEV, ctcm_chx_iofatal }, + { CTC_STATE_RXIDLE, CTC_EVENT_MC_FAIL, ctcm_chx_fail }, + { CTC_STATE_RXIDLE, CTC_EVENT_UC_ZERO, chx_rx }, + + { CTC_STATE_TXINIT, CTC_EVENT_STOP, ctcm_chx_haltio }, + { CTC_STATE_TXINIT, CTC_EVENT_START, fsm_action_nop }, + { CTC_STATE_TXINIT, CTC_EVENT_FINSTAT, ctcm_chx_txidle }, + { CTC_STATE_TXINIT, CTC_EVENT_UC_RCRESET, ctcm_chx_txiniterr }, + { CTC_STATE_TXINIT, CTC_EVENT_UC_RSRESET, ctcm_chx_txiniterr }, + { CTC_STATE_TXINIT, CTC_EVENT_TIMER, ctcm_chx_txiniterr }, + { CTC_STATE_TXINIT, CTC_EVENT_IO_ENODEV, ctcm_chx_iofatal }, + { CTC_STATE_TXINIT, CTC_EVENT_MC_FAIL, ctcm_chx_fail }, + + { CTC_STATE_TXIDLE, CTC_EVENT_STOP, ctcm_chx_haltio }, + { CTC_STATE_TXIDLE, CTC_EVENT_START, fsm_action_nop }, + { CTC_STATE_TXIDLE, CTC_EVENT_FINSTAT, chx_firstio }, + { CTC_STATE_TXIDLE, CTC_EVENT_UC_RCRESET, fsm_action_nop }, + { CTC_STATE_TXIDLE, CTC_EVENT_UC_RSRESET, fsm_action_nop }, + { CTC_STATE_TXIDLE, CTC_EVENT_IO_ENODEV, ctcm_chx_iofatal }, + { CTC_STATE_TXIDLE, CTC_EVENT_MC_FAIL, ctcm_chx_fail }, + + { CTC_STATE_TERM, CTC_EVENT_STOP, fsm_action_nop }, + { CTC_STATE_TERM, CTC_EVENT_START, ctcm_chx_restart }, + { CTC_STATE_TERM, CTC_EVENT_FINSTAT, ctcm_chx_stopped }, + { CTC_STATE_TERM, CTC_EVENT_UC_RCRESET, fsm_action_nop }, + { CTC_STATE_TERM, CTC_EVENT_UC_RSRESET, fsm_action_nop }, + { CTC_STATE_TERM, CTC_EVENT_MC_FAIL, ctcm_chx_fail }, + + { CTC_STATE_DTERM, CTC_EVENT_STOP, ctcm_chx_haltio }, + { CTC_STATE_DTERM, CTC_EVENT_START, ctcm_chx_restart }, + { CTC_STATE_DTERM, CTC_EVENT_FINSTAT, ctcm_chx_setmode }, + { CTC_STATE_DTERM, CTC_EVENT_UC_RCRESET, fsm_action_nop }, + { CTC_STATE_DTERM, CTC_EVENT_UC_RSRESET, fsm_action_nop }, + { CTC_STATE_DTERM, CTC_EVENT_MC_FAIL, ctcm_chx_fail }, + + { CTC_STATE_TX, CTC_EVENT_STOP, ctcm_chx_haltio }, + { CTC_STATE_TX, CTC_EVENT_START, fsm_action_nop }, + { CTC_STATE_TX, CTC_EVENT_FINSTAT, chx_txdone }, + { CTC_STATE_TX, CTC_EVENT_UC_RCRESET, ctcm_chx_txretry }, + { CTC_STATE_TX, CTC_EVENT_UC_RSRESET, ctcm_chx_txretry }, + { CTC_STATE_TX, CTC_EVENT_TIMER, ctcm_chx_txretry }, + { CTC_STATE_TX, CTC_EVENT_IO_ENODEV, ctcm_chx_iofatal }, + { CTC_STATE_TX, CTC_EVENT_MC_FAIL, ctcm_chx_fail }, + + { CTC_STATE_RXERR, CTC_EVENT_STOP, ctcm_chx_haltio }, + { CTC_STATE_TXERR, CTC_EVENT_STOP, ctcm_chx_haltio }, + { CTC_STATE_TXERR, CTC_EVENT_MC_FAIL, ctcm_chx_fail }, + { CTC_STATE_RXERR, CTC_EVENT_MC_FAIL, ctcm_chx_fail }, +}; + +int ch_fsm_len = ARRAY_SIZE(ch_fsm); + +/* + * MPC actions for mpc channel statemachine + * handling of MPC protocol requires extra + * statemachine and actions which are prefixed ctcmpc_ . + * The ctc_ch_states and ctc_ch_state_names, + * ctc_ch_events and ctc_ch_event_names share the ctcm definitions + * which are expanded by some elements. + */ + +/* + * Actions for mpc channel statemachine. + */ + +/** + * 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 ctcmpc_chx_txdone(fsm_instance *fi, int event, void *arg) +{ + struct channel *ch = (struct channel *)arg; + struct net_device *dev = ch->netdev; + struct ctcm_priv *privptr = dev->priv; + struct mpc_group *grpptr = privptr->mpcg; + struct sk_buff *skb; + int first = 1; + int i; + struct timespec done_stamp; + __u32 data_space; + unsigned long duration; + struct sk_buff *peekskb; + int rc; + struct th_header *header; + struct pdu *p_header; + + if (do_debug) + ctcm_pr_debug("%s cp:%i enter: %s()\n", + dev->name, smp_processor_id(), __FUNCTION__); + + done_stamp = current_kernel_time(); /* xtime */ + duration = (done_stamp.tv_sec - ch->prof.send_stamp.tv_sec) * 1000000 + + (done_stamp.tv_nsec - ch->prof.send_stamp.tv_nsec) / 1000; + if (duration > ch->prof.tx_time) + ch->prof.tx_time = duration; + + if (ch->irb->scsw.count != 0) + ctcm_pr_debug("%s: TX not complete, remaining %d bytes\n", + dev->name, ch->irb->scsw.count); + fsm_deltimer(&ch->timer); + while ((skb = skb_dequeue(&ch->io_queue))) { + privptr->stats.tx_packets++; + privptr->stats.tx_bytes += skb->len - TH_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) || (grpptr->in_sweep != 0)) { + spin_unlock(&ch->collect_lock); + fsm_newstate(fi, CTC_STATE_TXIDLE); + goto done; + } + + if (ctcm_checkalloc_buffer(ch)) { + spin_unlock(&ch->collect_lock); + goto done; + } + ch->trans_skb->data = ch->trans_skb_data; + skb_reset_tail_pointer(ch->trans_skb); + ch->trans_skb->len = 0; + if (ch->prof.maxmulti < (ch->collect_len + TH_HEADER_LENGTH)) + ch->prof.maxmulti = ch->collect_len + TH_HEADER_LENGTH; + if (ch->prof.maxcqueue < skb_queue_len(&ch->collect_queue)) + ch->prof.maxcqueue = skb_queue_len(&ch->collect_queue); + i = 0; + + if (do_debug_data) + ctcm_pr_debug("ctcmpc: %s() building " + "trans_skb from collect_q \n", __FUNCTION__); + + data_space = grpptr->group_max_buflen - TH_HEADER_LENGTH; + + if (do_debug_data) + ctcm_pr_debug("ctcmpc: %s() building trans_skb from collect_q" + " data_space:%04x\n", __FUNCTION__, data_space); + p_header = NULL; + while ((skb = skb_dequeue(&ch->collect_queue))) { + memcpy(skb_put(ch->trans_skb, skb->len), skb->data, skb->len); + p_header = (struct pdu *) + (skb_tail_pointer(ch->trans_skb) - skb->len); + p_header->pdu_flag = 0x00; + if (skb->protocol == ntohs(ETH_P_SNAP)) + p_header->pdu_flag |= 0x60; + else + p_header->pdu_flag |= 0x20; + + if (do_debug_data) { + ctcm_pr_debug("ctcmpc: %s()trans_skb len:%04x \n", + __FUNCTION__, ch->trans_skb->len); + ctcm_pr_debug("ctcmpc: %s() pdu header and data" + " for up to 32 bytes sent to vtam\n", + __FUNCTION__); + ctcmpc_dumpit((char *)p_header, min((int)skb->len, 32)); + } + ch->collect_len -= skb->len; + data_space -= skb->len; + privptr->stats.tx_packets++; + privptr->stats.tx_bytes += skb->len; + atomic_dec(&skb->users); + dev_kfree_skb_any(skb); + peekskb = skb_peek(&ch->collect_queue); + if (peekskb->len > data_space) + break; + i++; + } + /* p_header points to the last one we handled */ + if (p_header) + p_header->pdu_flag |= PDU_LAST; /*Say it's the last one*/ + header = kzalloc(TH_HEADER_LENGTH, gfp_type()); + + if (!header) { + printk(KERN_WARNING "ctcmpc: OUT OF MEMORY IN %s()" + ": Data Lost \n", __FUNCTION__); + spin_unlock(&ch->collect_lock); + fsm_event(privptr->mpcg->fsm, MPCG_EVENT_INOP, dev); + goto done; + } + + header->th_ch_flag = TH_HAS_PDU; /* Normal data */ + ch->th_seq_num++; + header->th_seq_num = ch->th_seq_num; + + if (do_debug_data) + ctcm_pr_debug("%s: ToVTAM_th_seq= %08x\n" , + __FUNCTION__, ch->th_seq_num); + + memcpy(skb_push(ch->trans_skb, TH_HEADER_LENGTH), header, + TH_HEADER_LENGTH); /* put the TH on the packet */ + + kfree(header); + + if (do_debug_data) { + ctcm_pr_debug("ctcmpc: %s()trans_skb len:%04x \n", + __FUNCTION__, ch->trans_skb->len); + + ctcm_pr_debug("ctcmpc: %s() up-to-50 bytes of trans_skb " + "data to vtam from collect_q\n", __FUNCTION__); + ctcmpc_dumpit((char *)ch->trans_skb->data, + min((int)ch->trans_skb->len, 50)); + } + + spin_unlock(&ch->collect_lock); + clear_normalized_cda(&ch->ccw[1]); + if (set_normalized_cda(&ch->ccw[1], ch->trans_skb->data)) { + dev_kfree_skb_any(ch->trans_skb); + ch->trans_skb = NULL; + printk(KERN_WARNING + "ctcmpc: %s()CCW failure - data lost\n", + __FUNCTION__); + fsm_event(privptr->mpcg->fsm, MPCG_EVENT_INOP, dev); + return; + } + ch->ccw[1].count = ch->trans_skb->len; + fsm_addtimer(&ch->timer, CTCM_TIME_5_SEC, CTC_EVENT_TIMER, ch); + ch->prof.send_stamp = current_kernel_time(); /* xtime */ + if (do_debug_ccw) + ctcmpc_dumpit((char *)&ch->ccw[0], sizeof(struct ccw1) * 3); + rc = ccw_device_start(ch->cdev, &ch->ccw[0], + (unsigned long)ch, 0xff, 0); + ch->prof.doios_multi++; + if (rc != 0) { + privptr->stats.tx_dropped += i; + privptr->stats.tx_errors += i; + fsm_deltimer(&ch->timer); + ctcm_ccw_check_rc(ch, rc, "chained TX"); + } +done: + ctcm_clear_busy(dev); + ctcm_pr_debug("ctcmpc exit: %s %s()\n", dev->name, __FUNCTION__); + return; +} + +/** + * 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 ctcmpc_chx_rx(fsm_instance *fi, int event, void *arg) +{ + struct channel *ch = (struct channel *)arg; + struct net_device *dev = ch->netdev; + struct ctcm_priv *privptr = dev->priv; + struct mpc_group *grpptr = privptr->mpcg; + struct sk_buff *skb = ch->trans_skb; + struct sk_buff *new_skb; + unsigned long saveflags = 0; /* avoids compiler warning */ + int len = ch->max_bufsize - ch->irb->scsw.count; + + if (do_debug_data) { + CTCM_DBF_TEXT_(TRACE, CTC_DBF_DEBUG, "mpc_ch_rx %s cp:%i %s\n", + dev->name, smp_processor_id(), ch->id); + CTCM_DBF_TEXT_(TRACE, CTC_DBF_DEBUG, "mpc_ch_rx: maxbuf: %04x " + "len: %04x\n", ch->max_bufsize, len); + } + fsm_deltimer(&ch->timer); + + if (skb == NULL) { + ctcm_pr_debug("ctcmpc exit: %s() TRANS_SKB = NULL \n", + __FUNCTION__); + goto again; + } + + if (len < TH_HEADER_LENGTH) { + ctcm_pr_info("%s: got packet with invalid length %d\n", + dev->name, len); + privptr->stats.rx_dropped++; + privptr->stats.rx_length_errors++; + } else { + /* must have valid th header or game over */ + __u32 block_len = len; + len = TH_HEADER_LENGTH + XID2_LENGTH + 4; + new_skb = __dev_alloc_skb(ch->max_bufsize, GFP_ATOMIC); + + if (new_skb == NULL) { + printk(KERN_INFO "ctcmpc:%s() NEW_SKB = NULL\n", + __FUNCTION__); + printk(KERN_WARNING "ctcmpc: %s() MEMORY ALLOC FAILED" + " - DATA LOST - MPC FAILED\n", + __FUNCTION__); + fsm_event(privptr->mpcg->fsm, MPCG_EVENT_INOP, dev); + goto again; + } + switch (fsm_getstate(grpptr->fsm)) { + case MPCG_STATE_RESET: + case MPCG_STATE_INOP: + dev_kfree_skb_any(new_skb); + break; + case MPCG_STATE_FLOWC: + case MPCG_STATE_READY: + memcpy(skb_put(new_skb, block_len), + skb->data, block_len); + skb_queue_tail(&ch->io_queue, new_skb); + tasklet_schedule(&ch->ch_tasklet); + break; + default: + memcpy(skb_put(new_skb, len), skb->data, len); + skb_queue_tail(&ch->io_queue, new_skb); + tasklet_hi_schedule(&ch->ch_tasklet); + break; + } + } + +again: + switch (fsm_getstate(grpptr->fsm)) { + int rc, dolock; + case MPCG_STATE_FLOWC: + case MPCG_STATE_READY: + if (ctcm_checkalloc_buffer(ch)) + break; + ch->trans_skb->data = ch->trans_skb_data; + skb_reset_tail_pointer(ch->trans_skb); + ch->trans_skb->len = 0; + ch->ccw[1].count = ch->max_bufsize; + if (do_debug_ccw) + ctcmpc_dumpit((char *)&ch->ccw[0], + sizeof(struct ccw1) * 3); + dolock = !in_irq(); + if (dolock) + spin_lock_irqsave( + get_ccwdev_lock(ch->cdev), saveflags); + rc = ccw_device_start(ch->cdev, &ch->ccw[0], + (unsigned long)ch, 0xff, 0); + if (dolock) /* see remark about conditional locking */ + spin_unlock_irqrestore( + get_ccwdev_lock(ch->cdev), saveflags); + if (rc != 0) + ctcm_ccw_check_rc(ch, rc, "normal RX"); + default: + break; + } + + if (do_debug) + ctcm_pr_debug("ctcmpc exit : %s %s(): ch=0x%p id=%s\n", + dev->name, __FUNCTION__, ch, ch->id); + +} + +/** + * 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 ctcmpc_chx_firstio(fsm_instance *fi, int event, void *arg) +{ + struct channel *ch = (struct channel *)arg; + struct net_device *dev = ch->netdev; + + if (do_debug) { + struct mpc_group *gptr = ((struct ctcm_priv *)dev->priv)->mpcg; + ctcm_pr_debug("ctcmpc enter: %s(): ch=0x%p id=%s\n", + __FUNCTION__, ch, ch->id); + ctcm_pr_debug("%s() %s chstate:%i grpstate:%i chprotocol:%i\n", + __FUNCTION__, ch->id, fsm_getstate(fi), + fsm_getstate(gptr->fsm), ch->protocol); + } + if (fsm_getstate(fi) == CTC_STATE_TXIDLE) + MPC_DBF_DEV_NAME(TRACE, dev, "remote side issued READ? "); + + fsm_deltimer(&ch->timer); + if (ctcm_checkalloc_buffer(ch)) + goto done; + + switch (fsm_getstate(fi)) { + case CTC_STATE_STARTRETRY: + case CTC_STATE_SETUPWAIT: + if (CHANNEL_DIRECTION(ch->flags) == READ) { + ctcmpc_chx_rxidle(fi, event, arg); + } else { + fsm_newstate(fi, CTC_STATE_TXIDLE); + fsm_event(((struct ctcm_priv *)dev->priv)->fsm, + DEV_EVENT_TXUP, dev); + } + goto done; + default: + break; + }; + + fsm_newstate(fi, (CHANNEL_DIRECTION(ch->flags) == READ) + ? CTC_STATE_RXINIT : CTC_STATE_TXINIT); + +done: + if (do_debug) + ctcm_pr_debug("ctcmpc exit : %s(): ch=0x%p id=%s\n", + __FUNCTION__, ch, ch->id); + return; +} + +/** + * 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. + */ +void ctcmpc_chx_rxidle(fsm_instance *fi, int event, void *arg) +{ + struct channel *ch = (struct channel *)arg; + struct net_device *dev = ch->netdev; + struct ctcm_priv *privptr = (struct ctcm_priv *)dev->priv; + struct mpc_group *grpptr = privptr->mpcg; + int rc; + unsigned long saveflags = 0; /* avoids compiler warning */ + + fsm_deltimer(&ch->timer); + ctcm_pr_debug("%s cp:%i enter: %s()\n", + dev->name, smp_processor_id(), __FUNCTION__); + if (do_debug) + ctcm_pr_debug("%s() %s chstate:%i grpstate:%i\n", + __FUNCTION__, ch->id, + fsm_getstate(fi), fsm_getstate(grpptr->fsm)); + + fsm_newstate(fi, CTC_STATE_RXIDLE); + /* XID processing complete */ + + switch (fsm_getstate(grpptr->fsm)) { + case MPCG_STATE_FLOWC: + case MPCG_STATE_READY: + if (ctcm_checkalloc_buffer(ch)) + goto done; + ch->trans_skb->data = ch->trans_skb_data; + skb_reset_tail_pointer(ch->trans_skb); + ch->trans_skb->len = 0; + ch->ccw[1].count = ch->max_bufsize; + if (do_debug_ccw) + ctcmpc_dumpit((char *)&ch->ccw[0], + sizeof(struct ccw1) * 3); + if (event == CTC_EVENT_START) + /* see remark about conditional locking */ + spin_lock_irqsave(get_ccwdev_lock(ch->cdev), saveflags); + rc = ccw_device_start(ch->cdev, &ch->ccw[0], + (unsigned long)ch, 0xff, 0); + if (event == CTC_EVENT_START) + spin_unlock_irqrestore( + get_ccwdev_lock(ch->cdev), saveflags); + if (rc != 0) { + fsm_newstate(fi, CTC_STATE_RXINIT); + ctcm_ccw_check_rc(ch, rc, "initial RX"); + goto done; + } + break; + default: + break; + } + + fsm_event(((struct ctcm_priv *)dev->priv)->fsm, DEV_EVENT_RXUP, dev); +done: + if (do_debug) + ctcm_pr_debug("ctcmpc exit: %s %s()\n", + dev->name, __FUNCTION__); + return; +} + +/* + * ctcmpc channel FSM action + * called from several points in ctcmpc_ch_fsm + * ctcmpc only + */ +static void ctcmpc_chx_attn(fsm_instance *fsm, int event, void *arg) +{ + struct channel *ch = (struct channel *)arg; + struct net_device *dev = ch->netdev; + struct ctcm_priv *privptr = (struct ctcm_priv *)dev->priv; + struct mpc_group *grpptr = privptr->mpcg; + + if (do_debug) { + ctcm_pr_debug("ctcmpc enter: %s(): cp=%i ch=0x%p id=%s" + "GrpState:%s ChState:%s\n", + __FUNCTION__, smp_processor_id(), ch, ch->id, + fsm_getstate_str(grpptr->fsm), + fsm_getstate_str(ch->fsm)); + } + + switch (fsm_getstate(grpptr->fsm)) { + case MPCG_STATE_XID2INITW: + /* ok..start yside xid exchanges */ + if (!ch->in_mpcgroup) + break; + if (fsm_getstate(ch->fsm) == CH_XID0_PENDING) { + fsm_deltimer(&grpptr->timer); + fsm_addtimer(&grpptr->timer, + MPC_XID_TIMEOUT_VALUE, + MPCG_EVENT_TIMER, dev); + fsm_event(grpptr->fsm, MPCG_EVENT_XID0DO, ch); + + } else if (fsm_getstate(ch->fsm) < CH_XID7_PENDING1) + /* attn rcvd before xid0 processed via bh */ + fsm_newstate(ch->fsm, CH_XID7_PENDING1); + break; + case MPCG_STATE_XID2INITX: + case MPCG_STATE_XID0IOWAIT: + case MPCG_STATE_XID0IOWAIX: + /* attn rcvd before xid0 processed on ch + but mid-xid0 processing for group */ + if (fsm_getstate(ch->fsm) < CH_XID7_PENDING1) + fsm_newstate(ch->fsm, CH_XID7_PENDING1); + break; + case MPCG_STATE_XID7INITW: + case MPCG_STATE_XID7INITX: + case MPCG_STATE_XID7INITI: + case MPCG_STATE_XID7INITZ: + switch (fsm_getstate(ch->fsm)) { + case CH_XID7_PENDING: + fsm_newstate(ch->fsm, CH_XID7_PENDING1); + break; + case CH_XID7_PENDING2: + fsm_newstate(ch->fsm, CH_XID7_PENDING3); + break; + } + fsm_event(grpptr->fsm, MPCG_EVENT_XID7DONE, dev); + break; + } + + if (do_debug) + ctcm_pr_debug("ctcmpc exit : %s(): cp=%i ch=0x%p id=%s\n", + __FUNCTION__, smp_processor_id(), ch, ch->id); + return; + +} + +/* + * ctcmpc channel FSM action + * called from one point in ctcmpc_ch_fsm + * ctcmpc only + */ +static void ctcmpc_chx_attnbusy(fsm_instance *fsm, int event, void *arg) +{ + struct channel *ch = (struct channel *)arg; + struct net_device *dev = ch->netdev; + struct ctcm_priv *privptr = (struct ctcm_priv *)dev->priv; + struct mpc_group *grpptr = privptr->mpcg; + + ctcm_pr_debug("ctcmpc enter: %s %s() %s \nGrpState:%s ChState:%s\n", + dev->name, + __FUNCTION__, ch->id, + fsm_getstate_str(grpptr->fsm), + fsm_getstate_str(ch->fsm)); + + fsm_deltimer(&ch->timer); + + switch (fsm_getstate(grpptr->fsm)) { + case MPCG_STATE_XID0IOWAIT: + /* vtam wants to be primary.start yside xid exchanges*/ + /* only receive one attn-busy at a time so must not */ + /* change state each time */ + grpptr->changed_side = 1; + fsm_newstate(grpptr->fsm, MPCG_STATE_XID2INITW); + break; + case MPCG_STATE_XID2INITW: + if (grpptr->changed_side == 1) { + grpptr->changed_side = 2; + break; + } + /* process began via call to establish_conn */ + /* so must report failure instead of reverting */ + /* back to ready-for-xid passive state */ + if (grpptr->estconnfunc) + goto done; + /* this attnbusy is NOT the result of xside xid */ + /* collisions so yside must have been triggered */ + /* by an ATTN that was not intended to start XID */ + /* processing. Revert back to ready-for-xid and */ + /* wait for ATTN interrupt to signal xid start */ + if (fsm_getstate(ch->fsm) == CH_XID0_INPROGRESS) { + fsm_newstate(ch->fsm, CH_XID0_PENDING) ; + fsm_deltimer(&grpptr->timer); + goto done; + } + fsm_event(grpptr->fsm, MPCG_EVENT_INOP, dev); + goto done; + case MPCG_STATE_XID2INITX: + /* XID2 was received before ATTN Busy for second + channel.Send yside xid for second channel. + */ + if (grpptr->changed_side == 1) { + grpptr->changed_side = 2; + break; + } + case MPCG_STATE_XID0IOWAIX: + case MPCG_STATE_XID7INITW: + case MPCG_STATE_XID7INITX: + case MPCG_STATE_XID7INITI: + case MPCG_STATE_XID7INITZ: + default: + /* multiple attn-busy indicates too out-of-sync */ + /* and they are certainly not being received as part */ + /* of valid mpc group negotiations.. */ + fsm_event(grpptr->fsm, MPCG_EVENT_INOP, dev); + goto done; + } + + if (grpptr->changed_side == 1) { + fsm_deltimer(&grpptr->timer); + fsm_addtimer(&grpptr->timer, MPC_XID_TIMEOUT_VALUE, + MPCG_EVENT_TIMER, dev); + } + if (ch->in_mpcgroup) + fsm_event(grpptr->fsm, MPCG_EVENT_XID0DO, ch); + else + printk(KERN_WARNING "ctcmpc: %s() Not all channels have" + " been added to group\n", __FUNCTION__); + +done: + if (do_debug) + ctcm_pr_debug("ctcmpc exit : %s()%s ch=0x%p id=%s\n", + __FUNCTION__, dev->name, ch, ch->id); + + return; + +} + +/* + * ctcmpc channel FSM action + * called from several points in ctcmpc_ch_fsm + * ctcmpc only + */ +static void ctcmpc_chx_resend(fsm_instance *fsm, int event, void *arg) +{ + struct channel *ch = (struct channel *)arg; + struct net_device *dev = ch->netdev; + struct ctcm_priv *privptr = (struct ctcm_priv *)dev->priv; + struct mpc_group *grpptr = privptr->mpcg; + + ctcm_pr_debug("ctcmpc enter: %s %s() %s \nGrpState:%s ChState:%s\n", + dev->name, __FUNCTION__, ch->id, + fsm_getstate_str(grpptr->fsm), + fsm_getstate_str(ch->fsm)); + + fsm_event(grpptr->fsm, MPCG_EVENT_XID0DO, ch); + + return; +} + +/* + * ctcmpc channel FSM action + * called from several points in ctcmpc_ch_fsm + * ctcmpc only + */ +static void ctcmpc_chx_send_sweep(fsm_instance *fsm, int event, void *arg) +{ + struct channel *ach = (struct channel *)arg; + struct net_device *dev = ach->netdev; + struct ctcm_priv *privptr = (struct ctcm_priv *)dev->priv; + struct mpc_group *grpptr = privptr->mpcg; + struct channel *wch = privptr->channel[WRITE]; + struct channel *rch = privptr->channel[READ]; + struct sk_buff *skb; + struct th_sweep *header; + int rc = 0; + unsigned long saveflags = 0; + + if (do_debug) + ctcm_pr_debug("ctcmpc enter: %s(): cp=%i ch=0x%p id=%s\n", + __FUNCTION__, smp_processor_id(), ach, ach->id); + + if (grpptr->in_sweep == 0) + goto done; + + if (do_debug_data) { + ctcm_pr_debug("ctcmpc: %s() 1: ToVTAM_th_seq= %08x\n" , + __FUNCTION__, wch->th_seq_num); + ctcm_pr_debug("ctcmpc: %s() 1: FromVTAM_th_seq= %08x\n" , + __FUNCTION__, rch->th_seq_num); + } + + if (fsm_getstate(wch->fsm) != CTC_STATE_TXIDLE) { + /* give the previous IO time to complete */ + fsm_addtimer(&wch->sweep_timer, + 200, CTC_EVENT_RSWEEP_TIMER, wch); + goto done; + } + + skb = skb_dequeue(&wch->sweep_queue); + if (!skb) + goto done; + + if (set_normalized_cda(&wch->ccw[4], skb->data)) { + grpptr->in_sweep = 0; + ctcm_clear_busy_do(dev); + dev_kfree_skb_any(skb); + fsm_event(grpptr->fsm, MPCG_EVENT_INOP, dev); + goto done; + } else { + atomic_inc(&skb->users); + skb_queue_tail(&wch->io_queue, skb); + } + + /* send out the sweep */ + wch->ccw[4].count = skb->len; + + header = (struct th_sweep *)skb->data; + switch (header->th.th_ch_flag) { + case TH_SWEEP_REQ: + grpptr->sweep_req_pend_num--; + break; + case TH_SWEEP_RESP: + grpptr->sweep_rsp_pend_num--; + break; + } + + header->sw.th_last_seq = wch->th_seq_num; + + if (do_debug_ccw) + ctcmpc_dumpit((char *)&wch->ccw[3], sizeof(struct ccw1) * 3); + + ctcm_pr_debug("ctcmpc: %s() sweep packet\n", __FUNCTION__); + ctcmpc_dumpit((char *)header, TH_SWEEP_LENGTH); + + fsm_addtimer(&wch->timer, CTCM_TIME_5_SEC, CTC_EVENT_TIMER, wch); + fsm_newstate(wch->fsm, CTC_STATE_TX); + + spin_lock_irqsave(get_ccwdev_lock(wch->cdev), saveflags); + wch->prof.send_stamp = current_kernel_time(); /* xtime */ + rc = ccw_device_start(wch->cdev, &wch->ccw[3], + (unsigned long) wch, 0xff, 0); + spin_unlock_irqrestore(get_ccwdev_lock(wch->cdev), saveflags); + + if ((grpptr->sweep_req_pend_num == 0) && + (grpptr->sweep_rsp_pend_num == 0)) { + grpptr->in_sweep = 0; + rch->th_seq_num = 0x00; + wch->th_seq_num = 0x00; + ctcm_clear_busy_do(dev); + } + + if (do_debug_data) { + ctcm_pr_debug("ctcmpc: %s()2: ToVTAM_th_seq= %08x\n" , + __FUNCTION__, wch->th_seq_num); + ctcm_pr_debug("ctcmpc: %s()2: FromVTAM_th_seq= %08x\n" , + __FUNCTION__, rch->th_seq_num); + } + + if (rc != 0) + ctcm_ccw_check_rc(wch, rc, "send sweep"); + +done: + if (do_debug) + ctcm_pr_debug("ctcmpc exit: %s() %s\n", __FUNCTION__, ach->id); + return; +} + + +/* + * The ctcmpc statemachine for a channel. + */ + +const fsm_node ctcmpc_ch_fsm[] = { + { CTC_STATE_STOPPED, CTC_EVENT_STOP, fsm_action_nop }, + { CTC_STATE_STOPPED, CTC_EVENT_START, ctcm_chx_start }, + { CTC_STATE_STOPPED, CTC_EVENT_IO_ENODEV, ctcm_chx_iofatal }, + { CTC_STATE_STOPPED, CTC_EVENT_FINSTAT, fsm_action_nop }, + { CTC_STATE_STOPPED, CTC_EVENT_MC_FAIL, fsm_action_nop }, + + { CTC_STATE_NOTOP, CTC_EVENT_STOP, ctcm_chx_stop }, + { CTC_STATE_NOTOP, CTC_EVENT_START, fsm_action_nop }, + { CTC_STATE_NOTOP, CTC_EVENT_FINSTAT, fsm_action_nop }, + { CTC_STATE_NOTOP, CTC_EVENT_MC_FAIL, fsm_action_nop }, + { CTC_STATE_NOTOP, CTC_EVENT_MC_GOOD, ctcm_chx_start }, + { CTC_STATE_NOTOP, CTC_EVENT_UC_RCRESET, ctcm_chx_stop }, + { CTC_STATE_NOTOP, CTC_EVENT_UC_RSRESET, ctcm_chx_stop }, + { CTC_STATE_NOTOP, CTC_EVENT_IO_ENODEV, ctcm_chx_iofatal }, + + { CTC_STATE_STARTWAIT, CTC_EVENT_STOP, ctcm_chx_haltio }, + { CTC_STATE_STARTWAIT, CTC_EVENT_START, fsm_action_nop }, + { CTC_STATE_STARTWAIT, CTC_EVENT_FINSTAT, ctcm_chx_setmode }, + { CTC_STATE_STARTWAIT, CTC_EVENT_TIMER, ctcm_chx_setuperr }, + { CTC_STATE_STARTWAIT, CTC_EVENT_IO_ENODEV, ctcm_chx_iofatal }, + { CTC_STATE_STARTWAIT, CTC_EVENT_MC_FAIL, ctcm_chx_fail }, + + { CTC_STATE_STARTRETRY, CTC_EVENT_STOP, ctcm_chx_haltio }, + { CTC_STATE_STARTRETRY, CTC_EVENT_TIMER, ctcm_chx_setmode }, + { CTC_STATE_STARTRETRY, CTC_EVENT_FINSTAT, ctcm_chx_setmode }, + { CTC_STATE_STARTRETRY, CTC_EVENT_MC_FAIL, ctcm_chx_fail }, + { CTC_STATE_STARTRETRY, CTC_EVENT_IO_ENODEV, ctcm_chx_iofatal }, + + { CTC_STATE_SETUPWAIT, CTC_EVENT_STOP, ctcm_chx_haltio }, + { CTC_STATE_SETUPWAIT, CTC_EVENT_START, fsm_action_nop }, + { CTC_STATE_SETUPWAIT, CTC_EVENT_FINSTAT, ctcmpc_chx_firstio }, + { CTC_STATE_SETUPWAIT, CTC_EVENT_UC_RCRESET, ctcm_chx_setuperr }, + { CTC_STATE_SETUPWAIT, CTC_EVENT_UC_RSRESET, ctcm_chx_setuperr }, + { CTC_STATE_SETUPWAIT, CTC_EVENT_TIMER, ctcm_chx_setmode }, + { CTC_STATE_SETUPWAIT, CTC_EVENT_IO_ENODEV, ctcm_chx_iofatal }, + { CTC_STATE_SETUPWAIT, CTC_EVENT_MC_FAIL, ctcm_chx_fail }, + + { CTC_STATE_RXINIT, CTC_EVENT_STOP, ctcm_chx_haltio }, + { CTC_STATE_RXINIT, CTC_EVENT_START, fsm_action_nop }, + { CTC_STATE_RXINIT, CTC_EVENT_FINSTAT, ctcmpc_chx_rxidle }, + { CTC_STATE_RXINIT, CTC_EVENT_UC_RCRESET, ctcm_chx_rxiniterr }, + { CTC_STATE_RXINIT, CTC_EVENT_UC_RSRESET, ctcm_chx_rxiniterr }, + { CTC_STATE_RXINIT, CTC_EVENT_TIMER, ctcm_chx_rxiniterr }, + { CTC_STATE_RXINIT, CTC_EVENT_ATTNBUSY, ctcm_chx_rxinitfail }, + { CTC_STATE_RXINIT, CTC_EVENT_IO_ENODEV, ctcm_chx_iofatal }, + { CTC_STATE_RXINIT, CTC_EVENT_UC_ZERO, ctcmpc_chx_firstio }, + { CTC_STATE_RXINIT, CTC_EVENT_MC_FAIL, ctcm_chx_fail }, + + { CH_XID0_PENDING, CTC_EVENT_FINSTAT, fsm_action_nop }, + { CH_XID0_PENDING, CTC_EVENT_ATTN, ctcmpc_chx_attn }, + { CH_XID0_PENDING, CTC_EVENT_STOP, ctcm_chx_haltio }, + { CH_XID0_PENDING, CTC_EVENT_START, fsm_action_nop }, + { CH_XID0_PENDING, CTC_EVENT_IO_ENODEV, ctcm_chx_iofatal }, + { CH_XID0_PENDING, CTC_EVENT_MC_FAIL, ctcm_chx_fail }, + { CH_XID0_PENDING, CTC_EVENT_UC_RCRESET, ctcm_chx_setuperr }, + { CH_XID0_PENDING, CTC_EVENT_UC_RSRESET, ctcm_chx_setuperr }, + { CH_XID0_PENDING, CTC_EVENT_UC_RSRESET, ctcm_chx_setuperr }, + { CH_XID0_PENDING, CTC_EVENT_ATTNBUSY, ctcm_chx_iofatal }, + + { CH_XID0_INPROGRESS, CTC_EVENT_FINSTAT, ctcmpc_chx_rx }, + { CH_XID0_INPROGRESS, CTC_EVENT_ATTN, ctcmpc_chx_attn }, + { CH_XID0_INPROGRESS, CTC_EVENT_STOP, ctcm_chx_haltio }, + { CH_XID0_INPROGRESS, CTC_EVENT_START, fsm_action_nop }, + { CH_XID0_INPROGRESS, CTC_EVENT_IO_ENODEV, ctcm_chx_iofatal }, + { CH_XID0_INPROGRESS, CTC_EVENT_MC_FAIL, ctcm_chx_fail }, + { CH_XID0_INPROGRESS, CTC_EVENT_UC_ZERO, ctcmpc_chx_rx }, + { CH_XID0_INPROGRESS, CTC_EVENT_UC_RCRESET, ctcm_chx_setuperr }, + { CH_XID0_INPROGRESS, CTC_EVENT_ATTNBUSY, ctcmpc_chx_attnbusy }, + { CH_XID0_INPROGRESS, CTC_EVENT_TIMER, ctcmpc_chx_resend }, + { CH_XID0_INPROGRESS, CTC_EVENT_IO_EBUSY, ctcm_chx_fail }, + + { CH_XID7_PENDING, CTC_EVENT_FINSTAT, ctcmpc_chx_rx }, + { CH_XID7_PENDING, CTC_EVENT_ATTN, ctcmpc_chx_attn }, + { CH_XID7_PENDING, CTC_EVENT_STOP, ctcm_chx_haltio }, + { CH_XID7_PENDING, CTC_EVENT_START, fsm_action_nop }, + { CH_XID7_PENDING, CTC_EVENT_IO_ENODEV, ctcm_chx_iofatal }, + { CH_XID7_PENDING, CTC_EVENT_MC_FAIL, ctcm_chx_fail }, + { CH_XID7_PENDING, CTC_EVENT_UC_ZERO, ctcmpc_chx_rx }, + { CH_XID7_PENDING, CTC_EVENT_UC_RCRESET, ctcm_chx_setuperr }, + { CH_XID7_PENDING, CTC_EVENT_UC_RSRESET, ctcm_chx_setuperr }, + { CH_XID7_PENDING, CTC_EVENT_UC_RSRESET, ctcm_chx_setuperr }, + { CH_XID7_PENDING, CTC_EVENT_ATTNBUSY, ctcm_chx_iofatal }, + { CH_XID7_PENDING, CTC_EVENT_TIMER, ctcmpc_chx_resend }, + { CH_XID7_PENDING, CTC_EVENT_IO_EBUSY, ctcm_chx_fail }, + + { CH_XID7_PENDING1, CTC_EVENT_FINSTAT, ctcmpc_chx_rx }, + { CH_XID7_PENDING1, CTC_EVENT_ATTN, ctcmpc_chx_attn }, + { CH_XID7_PENDING1, CTC_EVENT_STOP, ctcm_chx_haltio }, + { CH_XID7_PENDING1, CTC_EVENT_START, fsm_action_nop }, + { CH_XID7_PENDING1, CTC_EVENT_IO_ENODEV, ctcm_chx_iofatal }, + { CH_XID7_PENDING1, CTC_EVENT_MC_FAIL, ctcm_chx_fail }, + { CH_XID7_PENDING1, CTC_EVENT_UC_ZERO, ctcmpc_chx_rx }, + { CH_XID7_PENDING1, CTC_EVENT_UC_RCRESET, ctcm_chx_setuperr }, + { CH_XID7_PENDING1, CTC_EVENT_UC_RSRESET, ctcm_chx_setuperr }, + { CH_XID7_PENDING1, CTC_EVENT_ATTNBUSY, ctcm_chx_iofatal }, + { CH_XID7_PENDING1, CTC_EVENT_TIMER, ctcmpc_chx_resend }, + { CH_XID7_PENDING1, CTC_EVENT_IO_EBUSY, ctcm_chx_fail }, + + { CH_XID7_PENDING2, CTC_EVENT_FINSTAT, ctcmpc_chx_rx }, + { CH_XID7_PENDING2, CTC_EVENT_ATTN, ctcmpc_chx_attn }, + { CH_XID7_PENDING2, CTC_EVENT_STOP, ctcm_chx_haltio }, + { CH_XID7_PENDING2, CTC_EVENT_START, fsm_action_nop }, + { CH_XID7_PENDING2, CTC_EVENT_IO_ENODEV, ctcm_chx_iofatal }, + { CH_XID7_PENDING2, CTC_EVENT_MC_FAIL, ctcm_chx_fail }, + { CH_XID7_PENDING2, CTC_EVENT_UC_ZERO, ctcmpc_chx_rx }, + { CH_XID7_PENDING2, CTC_EVENT_UC_RCRESET, ctcm_chx_setuperr }, + { CH_XID7_PENDING2, CTC_EVENT_UC_RSRESET, ctcm_chx_setuperr }, + { CH_XID7_PENDING2, CTC_EVENT_ATTNBUSY, ctcm_chx_iofatal }, + { CH_XID7_PENDING2, CTC_EVENT_TIMER, ctcmpc_chx_resend }, + { CH_XID7_PENDING2, CTC_EVENT_IO_EBUSY, ctcm_chx_fail }, + + { CH_XID7_PENDING3, CTC_EVENT_FINSTAT, ctcmpc_chx_rx }, + { CH_XID7_PENDING3, CTC_EVENT_ATTN, ctcmpc_chx_attn }, + { CH_XID7_PENDING3, CTC_EVENT_STOP, ctcm_chx_haltio }, + { CH_XID7_PENDING3, CTC_EVENT_START, fsm_action_nop }, + { CH_XID7_PENDING3, CTC_EVENT_IO_ENODEV, ctcm_chx_iofatal }, + { CH_XID7_PENDING3, CTC_EVENT_MC_FAIL, ctcm_chx_fail }, + { CH_XID7_PENDING3, CTC_EVENT_UC_ZERO, ctcmpc_chx_rx }, + { CH_XID7_PENDING3, CTC_EVENT_UC_RCRESET, ctcm_chx_setuperr }, + { CH_XID7_PENDING3, CTC_EVENT_UC_RSRESET, ctcm_chx_setuperr }, + { CH_XID7_PENDING3, CTC_EVENT_ATTNBUSY, ctcm_chx_iofatal }, + { CH_XID7_PENDING3, CTC_EVENT_TIMER, ctcmpc_chx_resend }, + { CH_XID7_PENDING3, CTC_EVENT_IO_EBUSY, ctcm_chx_fail }, + + { CH_XID7_PENDING4, CTC_EVENT_FINSTAT, ctcmpc_chx_rx }, + { CH_XID7_PENDING4, CTC_EVENT_ATTN, ctcmpc_chx_attn }, + { CH_XID7_PENDING4, CTC_EVENT_STOP, ctcm_chx_haltio }, + { CH_XID7_PENDING4, CTC_EVENT_START, fsm_action_nop }, + { CH_XID7_PENDING4, CTC_EVENT_IO_ENODEV, ctcm_chx_iofatal }, + { CH_XID7_PENDING4, CTC_EVENT_MC_FAIL, ctcm_chx_fail }, + { CH_XID7_PENDING4, CTC_EVENT_UC_ZERO, ctcmpc_chx_rx }, + { CH_XID7_PENDING4, CTC_EVENT_UC_RCRESET, ctcm_chx_setuperr }, + { CH_XID7_PENDING4, CTC_EVENT_UC_RSRESET, ctcm_chx_setuperr }, + { CH_XID7_PENDING4, CTC_EVENT_ATTNBUSY, ctcm_chx_iofatal }, + { CH_XID7_PENDING4, CTC_EVENT_TIMER, ctcmpc_chx_resend }, + { CH_XID7_PENDING4, CTC_EVENT_IO_EBUSY, ctcm_chx_fail }, + + { CTC_STATE_RXIDLE, CTC_EVENT_STOP, ctcm_chx_haltio }, + { CTC_STATE_RXIDLE, CTC_EVENT_START, fsm_action_nop }, + { CTC_STATE_RXIDLE, CTC_EVENT_FINSTAT, ctcmpc_chx_rx }, + { CTC_STATE_RXIDLE, CTC_EVENT_UC_RCRESET, ctcm_chx_rxdisc }, + { CTC_STATE_RXIDLE, CTC_EVENT_UC_RSRESET, ctcm_chx_fail }, + { CTC_STATE_RXIDLE, CTC_EVENT_IO_ENODEV, ctcm_chx_iofatal }, + { CTC_STATE_RXIDLE, CTC_EVENT_MC_FAIL, ctcm_chx_fail }, + { CTC_STATE_RXIDLE, CTC_EVENT_UC_ZERO, ctcmpc_chx_rx }, + + { CTC_STATE_TXINIT, CTC_EVENT_STOP, ctcm_chx_haltio }, + { CTC_STATE_TXINIT, CTC_EVENT_START, fsm_action_nop }, + { CTC_STATE_TXINIT, CTC_EVENT_FINSTAT, ctcm_chx_txidle }, + { CTC_STATE_TXINIT, CTC_EVENT_UC_RCRESET, ctcm_chx_txiniterr }, + { CTC_STATE_TXINIT, CTC_EVENT_UC_RSRESET, ctcm_chx_txiniterr }, + { CTC_STATE_TXINIT, CTC_EVENT_TIMER, ctcm_chx_txiniterr }, + { CTC_STATE_TXINIT, CTC_EVENT_IO_ENODEV, ctcm_chx_iofatal }, + { CTC_STATE_TXINIT, CTC_EVENT_MC_FAIL, ctcm_chx_fail }, + { CTC_STATE_TXINIT, CTC_EVENT_RSWEEP_TIMER, ctcmpc_chx_send_sweep }, + + { CTC_STATE_TXIDLE, CTC_EVENT_STOP, ctcm_chx_haltio }, + { CTC_STATE_TXIDLE, CTC_EVENT_START, fsm_action_nop }, + { CTC_STATE_TXIDLE, CTC_EVENT_FINSTAT, ctcmpc_chx_firstio }, + { CTC_STATE_TXIDLE, CTC_EVENT_UC_RCRESET, ctcm_chx_fail }, + { CTC_STATE_TXIDLE, CTC_EVENT_UC_RSRESET, ctcm_chx_fail }, + { CTC_STATE_TXIDLE, CTC_EVENT_IO_ENODEV, ctcm_chx_iofatal }, + { CTC_STATE_TXIDLE, CTC_EVENT_MC_FAIL, ctcm_chx_fail }, + { CTC_STATE_TXIDLE, CTC_EVENT_RSWEEP_TIMER, ctcmpc_chx_send_sweep }, + + { CTC_STATE_TERM, CTC_EVENT_STOP, fsm_action_nop }, + { CTC_STATE_TERM, CTC_EVENT_START, ctcm_chx_restart }, + { CTC_STATE_TERM, CTC_EVENT_FINSTAT, ctcm_chx_stopped }, + { CTC_STATE_TERM, CTC_EVENT_UC_RCRESET, fsm_action_nop }, + { CTC_STATE_TERM, CTC_EVENT_UC_RSRESET, fsm_action_nop }, + { CTC_STATE_TERM, CTC_EVENT_MC_FAIL, ctcm_chx_fail }, + { CTC_STATE_TERM, CTC_EVENT_IO_EBUSY, ctcm_chx_fail }, + { CTC_STATE_TERM, CTC_EVENT_IO_ENODEV, ctcm_chx_iofatal }, + + { CTC_STATE_DTERM, CTC_EVENT_STOP, ctcm_chx_haltio }, + { CTC_STATE_DTERM, CTC_EVENT_START, ctcm_chx_restart }, + { CTC_STATE_DTERM, CTC_EVENT_FINSTAT, ctcm_chx_setmode }, + { CTC_STATE_DTERM, CTC_EVENT_UC_RCRESET, fsm_action_nop }, + { CTC_STATE_DTERM, CTC_EVENT_UC_RSRESET, fsm_action_nop }, + { CTC_STATE_DTERM, CTC_EVENT_MC_FAIL, ctcm_chx_fail }, + { CTC_STATE_DTERM, CTC_EVENT_IO_ENODEV, ctcm_chx_iofatal }, + + { CTC_STATE_TX, CTC_EVENT_STOP, ctcm_chx_haltio }, + { CTC_STATE_TX, CTC_EVENT_START, fsm_action_nop }, + { CTC_STATE_TX, CTC_EVENT_FINSTAT, ctcmpc_chx_txdone }, + { CTC_STATE_TX, CTC_EVENT_UC_RCRESET, ctcm_chx_fail }, + { CTC_STATE_TX, CTC_EVENT_UC_RSRESET, ctcm_chx_fail }, + { CTC_STATE_TX, CTC_EVENT_TIMER, ctcm_chx_txretry }, + { CTC_STATE_TX, CTC_EVENT_IO_ENODEV, ctcm_chx_iofatal }, + { CTC_STATE_TX, CTC_EVENT_MC_FAIL, ctcm_chx_fail }, + { CTC_STATE_TX, CTC_EVENT_RSWEEP_TIMER, ctcmpc_chx_send_sweep }, + { CTC_STATE_TX, CTC_EVENT_IO_EBUSY, ctcm_chx_fail }, + + { CTC_STATE_RXERR, CTC_EVENT_STOP, ctcm_chx_haltio }, + { CTC_STATE_TXERR, CTC_EVENT_STOP, ctcm_chx_haltio }, + { CTC_STATE_TXERR, CTC_EVENT_IO_ENODEV, ctcm_chx_iofatal }, + { CTC_STATE_TXERR, CTC_EVENT_MC_FAIL, ctcm_chx_fail }, + { CTC_STATE_RXERR, CTC_EVENT_MC_FAIL, ctcm_chx_fail }, +}; + +int mpc_ch_fsm_len = ARRAY_SIZE(ctcmpc_ch_fsm); + +/* + * Actions for interface - statemachine. + */ + +/** + * Startup channels by sending CTC_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 struct net_device * upon call. + */ +static void dev_action_start(fsm_instance *fi, int event, void *arg) +{ + struct net_device *dev = (struct net_device *)arg; + struct ctcm_priv *privptr = dev->priv; + int direction; + + CTCMY_DBF_DEV_NAME(SETUP, dev, ""); + + fsm_deltimer(&privptr->restart_timer); + fsm_newstate(fi, DEV_STATE_STARTWAIT_RXTX); + if (IS_MPC(privptr)) + privptr->mpcg->channels_terminating = 0; + for (direction = READ; direction <= WRITE; direction++) { + struct channel *ch = privptr->channel[direction]; + fsm_event(ch->fsm, CTC_EVENT_START, ch); + } +} + +/** + * Shutdown channels by sending CTC_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 struct net_device * upon call. + */ +static void dev_action_stop(fsm_instance *fi, int event, void *arg) +{ + int direction; + struct net_device *dev = (struct net_device *)arg; + struct ctcm_priv *privptr = (struct ctcm_priv *)dev->priv; + + CTCMY_DBF_DEV_NAME(SETUP, dev, ""); + + fsm_newstate(fi, DEV_STATE_STOPWAIT_RXTX); + for (direction = READ; direction <= WRITE; direction++) { + struct channel *ch = privptr->channel[direction]; + fsm_event(ch->fsm, CTC_EVENT_STOP, ch); + ch->th_seq_num = 0x00; + if (do_debug) + ctcm_pr_debug("ctcm: %s() CH_th_seq= %08x\n", + __FUNCTION__, ch->th_seq_num); + } + if (IS_MPC(privptr)) + fsm_newstate(privptr->mpcg->fsm, MPCG_STATE_RESET); +} + +static void dev_action_restart(fsm_instance *fi, int event, void *arg) +{ + int restart_timer; + struct net_device *dev = (struct net_device *)arg; + struct ctcm_priv *privptr = (struct ctcm_priv *)dev->priv; + + CTCMY_DBF_DEV_NAME(TRACE, dev, ""); + + if (IS_MPC(privptr)) { + ctcm_pr_info("ctcm: %s Restarting Device and " + "MPC Group in 5 seconds\n", + dev->name); + restart_timer = CTCM_TIME_1_SEC; + } else { + ctcm_pr_info("%s: Restarting\n", dev->name); + restart_timer = CTCM_TIME_5_SEC; + } + + dev_action_stop(fi, event, arg); + fsm_event(privptr->fsm, DEV_EVENT_STOP, dev); + if (IS_MPC(privptr)) + fsm_newstate(privptr->mpcg->fsm, MPCG_STATE_RESET); + + /* going back into start sequence too quickly can */ + /* result in the other side becoming unreachable due */ + /* to sense reported when IO is aborted */ + fsm_addtimer(&privptr->restart_timer, restart_timer, + DEV_EVENT_START, dev); +} + +/** + * 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 struct net_device * upon call. + */ +static void dev_action_chup(fsm_instance *fi, int event, void *arg) +{ + struct net_device *dev = (struct net_device *)arg; + struct ctcm_priv *privptr = (struct ctcm_priv *)dev->priv; + + CTCMY_DBF_DEV_NAME(SETUP, dev, ""); + + 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); + ctcm_pr_info("%s: connected with remote side\n", + dev->name); + ctcm_clear_busy(dev); + } + break; + case DEV_STATE_STARTWAIT_TX: + if (event == DEV_EVENT_TXUP) { + fsm_newstate(fi, DEV_STATE_RUNNING); + ctcm_pr_info("%s: connected with remote side\n", + dev->name); + ctcm_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; + } + + if (IS_MPC(privptr)) { + if (event == DEV_EVENT_RXUP) + mpc_channel_action(privptr->channel[READ], + READ, MPC_CHANNEL_ADD); + else + mpc_channel_action(privptr->channel[WRITE], + WRITE, MPC_CHANNEL_ADD); + } +} + +/** + * Called from device 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 struct net_device * upon call. + */ +static void dev_action_chdown(fsm_instance *fi, int event, void *arg) +{ + + struct net_device *dev = (struct net_device *)arg; + struct ctcm_priv *privptr = (struct ctcm_priv *)dev->priv; + + CTCMY_DBF_DEV_NAME(SETUP, dev, ""); + + switch (fsm_getstate(fi)) { + case DEV_STATE_RUNNING: + if (event == DEV_EVENT_TXDOWN) + fsm_newstate(fi, DEV_STATE_STARTWAIT_TX); + else + fsm_newstate(fi, DEV_STATE_STARTWAIT_RX); + break; + case DEV_STATE_STARTWAIT_RX: + if (event == DEV_EVENT_TXDOWN) + fsm_newstate(fi, DEV_STATE_STARTWAIT_RXTX); + break; + case DEV_STATE_STARTWAIT_TX: + if (event == DEV_EVENT_RXDOWN) + fsm_newstate(fi, DEV_STATE_STARTWAIT_RXTX); + break; + case DEV_STATE_STOPWAIT_RXTX: + if (event == DEV_EVENT_TXDOWN) + fsm_newstate(fi, DEV_STATE_STOPWAIT_RX); + else + fsm_newstate(fi, DEV_STATE_STOPWAIT_TX); + break; + case DEV_STATE_STOPWAIT_RX: + if (event == DEV_EVENT_RXDOWN) + fsm_newstate(fi, DEV_STATE_STOPPED); + break; + case DEV_STATE_STOPWAIT_TX: + if (event == DEV_EVENT_TXDOWN) + fsm_newstate(fi, DEV_STATE_STOPPED); + break; + } + if (IS_MPC(privptr)) { + if (event == DEV_EVENT_RXDOWN) + mpc_channel_action(privptr->channel[READ], + READ, MPC_CHANNEL_REMOVE); + else + mpc_channel_action(privptr->channel[WRITE], + WRITE, MPC_CHANNEL_REMOVE); + } +} + +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_RXTX, DEV_EVENT_RESTART, dev_action_restart }, + { 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_RX, DEV_EVENT_RESTART, dev_action_restart }, + { 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_STOPWAIT_TX, DEV_EVENT_RESTART, dev_action_restart }, + { 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_RXTX, DEV_EVENT_RESTART, dev_action_restart }, + { 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_TX, DEV_EVENT_RESTART, dev_action_restart }, + { 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_STARTWAIT_RX, DEV_EVENT_RESTART, dev_action_restart }, + { 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 }, + { DEV_STATE_RUNNING, DEV_EVENT_RESTART, dev_action_restart }, +}; + +int dev_fsm_len = ARRAY_SIZE(dev_fsm); + +/* --- This is the END my friend --- */ + Index: linux-2.6-uschi/drivers/s390/net/ctcm_fsms.h =================================================================== --- /dev/null +++ linux-2.6-uschi/drivers/s390/net/ctcm_fsms.h @@ -0,0 +1,359 @@ +/* + * drivers/s390/net/ctcm_fsms.h + * + * Copyright IBM Corp. 2001, 2007 + * Authors: Fritz Elfert (felfert@millenux.com) + * Peter Tiedemann (ptiedem@de.ibm.com) + * MPC additions : + * Belinda Thompson (belindat@us.ibm.com) + * Andy Richter (richtera@us.ibm.com) + */ +#ifndef _CTCM_FSMS_H_ +#define _CTCM_FSMS_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/bitops.h> + +#include <linux/signal.h> +#include <linux/string.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 <linux/io.h> +#include <asm/ccwdev.h> +#include <asm/ccwgroup.h> +#include <linux/uaccess.h> + +#include <asm/idals.h> + +#include "fsm.h" +#include "cu3088.h" +#include "ctcm_main.h" + +/* + * Definitions for the channel statemachine(s) for ctc and ctcmpc + * + * To allow better kerntyping, prefix-less definitions for channel states + * and channel events have been replaced : + * ch_event... -> ctc_ch_event... + * CH_EVENT... -> CTC_EVENT... + * ch_state... -> ctc_ch_state... + * CH_STATE... -> CTC_STATE... + */ +/* + * Events of the channel statemachine(s) for ctc and ctcmpc + */ +enum ctc_ch_events { + /* + * Events, representing return code of + * I/O operations (ccw_device_start, ccw_device_halt et al.) + */ + CTC_EVENT_IO_SUCCESS, + CTC_EVENT_IO_EBUSY, + CTC_EVENT_IO_ENODEV, + CTC_EVENT_IO_UNKNOWN, + + CTC_EVENT_ATTNBUSY, + CTC_EVENT_ATTN, + CTC_EVENT_BUSY, + /* + * Events, representing unit-check + */ + CTC_EVENT_UC_RCRESET, + CTC_EVENT_UC_RSRESET, + CTC_EVENT_UC_TXTIMEOUT, + CTC_EVENT_UC_TXPARITY, + CTC_EVENT_UC_HWFAIL, + CTC_EVENT_UC_RXPARITY, + CTC_EVENT_UC_ZERO, + CTC_EVENT_UC_UNKNOWN, + /* + * Events, representing subchannel-check + */ + CTC_EVENT_SC_UNKNOWN, + /* + * Events, representing machine checks + */ + CTC_EVENT_MC_FAIL, + CTC_EVENT_MC_GOOD, + /* + * Event, representing normal IRQ + */ + CTC_EVENT_IRQ, + CTC_EVENT_FINSTAT, + /* + * Event, representing timer expiry. + */ + CTC_EVENT_TIMER, + /* + * Events, representing commands from upper levels. + */ + CTC_EVENT_START, + CTC_EVENT_STOP, + CTC_NR_EVENTS, + /* + * additional MPC events + */ + CTC_EVENT_SEND_XID = CTC_NR_EVENTS, + CTC_EVENT_RSWEEP_TIMER, + /* + * MUST be always the last element!! + */ + CTC_MPC_NR_EVENTS, +}; + +/* + * States of the channel statemachine(s) for ctc and ctcmpc. + */ +enum ctc_ch_states { + /* + * Channel not assigned to any device, + * initial state, direction invalid + */ + CTC_STATE_IDLE, + /* + * Channel assigned but not operating + */ + CTC_STATE_STOPPED, + CTC_STATE_STARTWAIT, + CTC_STATE_STARTRETRY, + CTC_STATE_SETUPWAIT, + CTC_STATE_RXINIT, + CTC_STATE_TXINIT, + CTC_STATE_RX, + CTC_STATE_TX, + CTC_STATE_RXIDLE, + CTC_STATE_TXIDLE, + CTC_STATE_RXERR, + CTC_STATE_TXERR, + CTC_STATE_TERM, + CTC_STATE_DTERM, + CTC_STATE_NOTOP, + CTC_NR_STATES, /* MUST be the last element of non-expanded states */ + /* + * additional MPC states + */ + CH_XID0_PENDING = CTC_NR_STATES, + CH_XID0_INPROGRESS, + CH_XID7_PENDING, + CH_XID7_PENDING1, + CH_XID7_PENDING2, + CH_XID7_PENDING3, + CH_XID7_PENDING4, + CTC_MPC_NR_STATES, /* MUST be the last element of expanded mpc states */ +}; + +extern const char *ctc_ch_event_names[]; + +extern const char *ctc_ch_state_names[]; + +void ctcm_ccw_check_rc(struct channel *ch, int rc, char *msg); +void ctcm_purge_skb_queue(struct sk_buff_head *q); +void fsm_action_nop(fsm_instance *fi, int event, void *arg); + +/* + * ----- non-static actions for ctcm channel statemachine ----- + * + */ +void ctcm_chx_txidle(fsm_instance *fi, int event, void *arg); + +/* + * ----- FSM (state/event/action) of the ctcm channel statemachine ----- + */ +extern const fsm_node ch_fsm[]; +extern int ch_fsm_len; + + +/* + * ----- non-static actions for ctcmpc channel statemachine ---- + * + */ +/* shared : +void ctcm_chx_txidle(fsm_instance * fi, int event, void *arg); + */ +void ctcmpc_chx_rxidle(fsm_instance *fi, int event, void *arg); + +/* + * ----- FSM (state/event/action) of the ctcmpc channel statemachine ----- + */ +extern const fsm_node ctcmpc_ch_fsm[]; +extern int mpc_ch_fsm_len; + +/* + * Definitions for the device interface statemachine for ctc and mpc + */ + +/* + * States of the device 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!! + */ + CTCM_NR_DEV_STATES +}; + +extern const char *dev_state_names[]; + +/* + * Events of the device interface statemachine. + * ctcm and ctcmpc + */ +enum dev_events { + DEV_EVENT_START, + DEV_EVENT_STOP, + DEV_EVENT_RXUP, + DEV_EVENT_TXUP, + DEV_EVENT_RXDOWN, + DEV_EVENT_TXDOWN, + DEV_EVENT_RESTART, + /* + * MUST be always the last element!! + */ + CTCM_NR_DEV_EVENTS +}; + +extern const char *dev_event_names[]; + +/* + * Actions for the device interface statemachine. + * ctc and ctcmpc + */ +/* +static void dev_action_start(fsm_instance * fi, int event, void *arg); +static void dev_action_stop(fsm_instance * fi, int event, void *arg); +static void dev_action_restart(fsm_instance *fi, int event, void *arg); +static void dev_action_chup(fsm_instance * fi, int event, void *arg); +static void dev_action_chdown(fsm_instance * fi, int event, void *arg); +*/ + +/* + * The (state/event/action) fsm table of the device interface statemachine. + * ctcm and ctcmpc + */ +extern const fsm_node dev_fsm[]; +extern int dev_fsm_len; + + +/* + * Definitions for the MPC Group statemachine + */ + +/* + * MPC Group Station FSM States + +State Name When In This State +====================== ======================================= +MPCG_STATE_RESET Initial State When Driver Loaded + We receive and send NOTHING + +MPCG_STATE_INOP INOP Received. + Group level non-recoverable error + +MPCG_STATE_READY XID exchanges for at least 1 write and + 1 read channel have completed. + Group is ready for data transfer. + +States from ctc_mpc_alloc_channel +============================================================== +MPCG_STATE_XID2INITW Awaiting XID2(0) Initiation + ATTN from other side will start + XID negotiations. + Y-side protocol only. + +MPCG_STATE_XID2INITX XID2(0) negotiations are in progress. + At least 1, but not all, XID2(0)'s + have been received from partner. + +MPCG_STATE_XID7INITW XID2(0) complete + No XID2(7)'s have yet been received. + XID2(7) negotiations pending. + +MPCG_STATE_XID7INITX XID2(7) negotiations in progress. + At least 1, but not all, XID2(7)'s + have been received from partner. + +MPCG_STATE_XID7INITF XID2(7) negotiations complete. + Transitioning to READY. + +MPCG_STATE_READY Ready for Data Transfer. + + +States from ctc_mpc_establish_connectivity call +============================================================== +MPCG_STATE_XID0IOWAIT Initiating XID2(0) negotiations. + X-side protocol only. + ATTN-BUSY from other side will convert + this to Y-side protocol and the + ctc_mpc_alloc_channel flow will begin. + +MPCG_STATE_XID0IOWAIX XID2(0) negotiations are in progress. + At least 1, but not all, XID2(0)'s + have been received from partner. + +MPCG_STATE_XID7INITI XID2(0) complete + No XID2(7)'s have yet been received. + XID2(7) negotiations pending. + +MPCG_STATE_XID7INITZ XID2(7) negotiations in progress. + At least 1, but not all, XID2(7)'s + have been received from partner. + +MPCG_STATE_XID7INITF XID2(7) negotiations complete. + Transitioning to READY. + +MPCG_STATE_READY Ready for Data Transfer. + +*/ + +enum mpcg_events { + MPCG_EVENT_INOP, + MPCG_EVENT_DISCONC, + MPCG_EVENT_XID0DO, + MPCG_EVENT_XID2, + MPCG_EVENT_XID2DONE, + MPCG_EVENT_XID7DONE, + MPCG_EVENT_TIMER, + MPCG_EVENT_DOIO, + MPCG_NR_EVENTS, +}; + +enum mpcg_states { + MPCG_STATE_RESET, + MPCG_STATE_INOP, + MPCG_STATE_XID2INITW, + MPCG_STATE_XID2INITX, + MPCG_STATE_XID7INITW, + MPCG_STATE_XID7INITX, + MPCG_STATE_XID0IOWAIT, + MPCG_STATE_XID0IOWAIX, + MPCG_STATE_XID7INITI, + MPCG_STATE_XID7INITZ, + MPCG_STATE_XID7INITF, + MPCG_STATE_FLOWC, + MPCG_STATE_READY, + MPCG_NR_STATES, +}; + +#endif +/* --- This is the END my friend --- */ Index: linux-2.6-uschi/drivers/s390/net/ctcm_main.c =================================================================== --- /dev/null +++ linux-2.6-uschi/drivers/s390/net/ctcm_main.c @@ -0,0 +1,1772 @@ +/* + * drivers/s390/net/ctcm_main.c + * + * Copyright IBM Corp. 2001, 2007 + * Author(s): + * Original CTC driver(s): + * Fritz Elfert (felfert@millenux.com) + * Dieter Wellerdiek (wel@de.ibm.com) + * Martin Schwidefsky (schwidefsky@de.ibm.com) + * Denis Joseph Barrow (barrow_dj@yahoo.com) + * Jochen Roehrig (roehrig@de.ibm.com) + * Cornelia Huck <cornelia.huck@de.ibm.com> + * MPC additions: + * Belinda Thompson (belindat@us.ibm.com) + * Andy Richter (richtera@us.ibm.com) + * Revived by: + * Peter Tiedemann (ptiedem@de.ibm.com) + */ + +#undef DEBUG +#undef DEBUGDATA +#undef DEBUGCCW + +#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/bitops.h> + +#include <linux/signal.h> +#include <linux/string.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 <linux/io.h> +#include <asm/ccwdev.h> +#include <asm/ccwgroup.h> +#include <linux/uaccess.h> + +#include <asm/idals.h> + +#include "cu3088.h" +#include "ctcm_fsms.h" +#include "ctcm_main.h" + +/* Some common global variables */ + +/* + * Linked list of all detected channels. + */ +struct channel *channels; + +/** + * 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. + */ +void ctcm_unpack_skb(struct channel *ch, struct sk_buff *pskb) +{ + struct net_device *dev = ch->netdev; + struct ctcm_priv *privptr = (struct ctcm_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; + int skblen; + struct ll_header *header = (struct ll_header *)pskb->data; + + skb_pull(pskb, LL_HEADER_LENGTH); + if ((ch->protocol == CTCM_PROTO_S390) && + (header->type != ETH_P_IP)) { + + if (!(ch->logflags & LOG_FLAG_ILLEGALPKT)) { + /* + * Check packet type only if we stick strictly + * to S/390's protocol of OS390. This only + * supports IP. Otherwise allow any packet + * type. + */ + ctcm_pr_warn("%s Illegal packet type 0x%04x " + "received, dropping\n", + dev->name, header->type); + ch->logflags |= LOG_FLAG_ILLEGALPKT; + } + + privptr->stats.rx_dropped++; + privptr->stats.rx_frame_errors++; + return; + } + pskb->protocol = ntohs(header->type); + if (header->length <= LL_HEADER_LENGTH) { + if (!(ch->logflags & LOG_FLAG_ILLEGALSIZE)) { + ctcm_pr_warn( + "%s Illegal packet size %d " + "received (MTU=%d blocklen=%d), " + "dropping\n", dev->name, header->length, + dev->mtu, len); + ch->logflags |= LOG_FLAG_ILLEGALSIZE; + } + + privptr->stats.rx_dropped++; + privptr->stats.rx_length_errors++; + return; + } + header->length -= LL_HEADER_LENGTH; + len -= LL_HEADER_LENGTH; + if ((header->length > skb_tailroom(pskb)) || + (header->length > len)) { + if (!(ch->logflags & LOG_FLAG_OVERRUN)) { + ctcm_pr_warn( + "%s Illegal packet size %d (beyond the" + " end of received data), dropping\n", + dev->name, header->length); + ch->logflags |= LOG_FLAG_OVERRUN; + } + + privptr->stats.rx_dropped++; + privptr->stats.rx_length_errors++; + return; + } + skb_put(pskb, header->length); + skb_reset_mac_header(pskb); + len -= header->length; + skb = dev_alloc_skb(pskb->len); + if (!skb) { + if (!(ch->logflags & LOG_FLAG_NOMEM)) { + ctcm_pr_warn( + "%s Out of memory in ctcm_unpack_skb\n", + dev->name); + ch->logflags |= LOG_FLAG_NOMEM; + } + privptr->stats.rx_dropped++; + return; + } + skb_copy_from_linear_data(pskb, skb_put(skb, pskb->len), + pskb->len); + skb_reset_mac_header(skb); + skb->dev = pskb->dev; + skb->protocol = pskb->protocol; + pskb->ip_summed = CHECKSUM_UNNECESSARY; + skblen = skb->len; + /* + * reset logflags + */ + ch->logflags = 0; + privptr->stats.rx_packets++; + privptr->stats.rx_bytes += skblen; + netif_rx_ni(skb); + dev->last_rx = jiffies; + if (len > 0) { + skb_pull(pskb, header->length); + if (skb_tailroom(pskb) < LL_HEADER_LENGTH) { + if (!(ch->logflags & LOG_FLAG_OVERRUN)) { + CTCM_DBF_DEV_NAME(TRACE, dev, + "Overrun in ctcm_unpack_skb"); + ch->logflags |= LOG_FLAG_OVERRUN; + } + return; + } + skb_put(pskb, LL_HEADER_LENGTH); + } + } +} + +/** + * Release a specific channel in the channel list. + * + * @param ch Pointer to channel struct to be released. + */ +static void channel_free(struct channel *ch) +{ + CTCM_DBF_TEXT(TRACE, 2, __FUNCTION__); + ch->flags &= ~CHANNEL_FLAGS_INUSE; + fsm_newstate(ch->fsm, CTC_STATE_IDLE); +} + +/** + * Remove a specific channel in the channel list. + * + * @param ch Pointer to channel struct to be released. + */ +static void channel_remove(struct channel *ch) +{ + struct channel **c = &channels; + char chid[CTCM_ID_SIZE+1]; + int ok = 0; + + if (ch == NULL) + return; + else + strncpy(chid, ch->id, CTCM_ID_SIZE); + + channel_free(ch); + while (*c) { + if (*c == ch) { + *c = ch->next; + fsm_deltimer(&ch->timer); + if (IS_MPC(ch)) + fsm_deltimer(&ch->sweep_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_any(ch->trans_skb); + } + if (IS_MPC(ch)) { + tasklet_kill(&ch->ch_tasklet); + tasklet_kill(&ch->ch_disc_tasklet); + kfree(ch->discontact_th); + } + kfree(ch->ccw); + kfree(ch->irb); + kfree(ch); + ok = 1; + break; + } + c = &((*c)->next); + } + + CTCM_DBF_TEXT_(SETUP, CTC_DBF_INFO, "%s(%s) %s", CTCM_FUNTAIL, + chid, ok ? "OK" : "failed"); +} + +/** + * Get a specific channel from the channel list. + * + * @param type Type of channel we are interested in. + * @param id Id 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 struct channel *channel_get(enum channel_types type, + char *id, int direction) +{ + struct channel *ch = channels; + + if (do_debug) { + char buf[64]; + sprintf(buf, "%s(%d, %s, %d)\n", + CTCM_FUNTAIL, type, id, direction); + CTCM_DBF_TEXT(TRACE, CTC_DBF_INFO, buf); + } + while (ch && (strncmp(ch->id, id, CTCM_ID_SIZE) || (ch->type != type))) + ch = ch->next; + if (!ch) { + char buf[64]; + sprintf(buf, "%s(%d, %s, %d) not found in channel list\n", + CTCM_FUNTAIL, type, id, direction); + CTCM_DBF_TEXT(ERROR, CTC_DBF_ERROR, buf); + } 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, CTC_STATE_STOPPED); + } + } + return ch; +} + +static long ctcm_check_irb_error(struct ccw_device *cdev, struct irb *irb) +{ + if (!IS_ERR(irb)) + return 0; + + CTCM_DBF_TEXT_(ERROR, CTC_DBF_WARN, "irb error %ld on device %s\n", + PTR_ERR(irb), cdev->dev.bus_id); + + switch (PTR_ERR(irb)) { + case -EIO: + ctcm_pr_warn("i/o-error on device %s\n", cdev->dev.bus_id); + break; + case -ETIMEDOUT: + ctcm_pr_warn("timeout on device %s\n", cdev->dev.bus_id); + break; + default: + ctcm_pr_warn("unknown error %ld on device %s\n", + PTR_ERR(irb), cdev->dev.bus_id); + } + return PTR_ERR(irb); +} + + +/** + * Check sense of a unit check. + * + * @param ch The channel, the sense code belongs to. + * @param sense The sense code to inspect. + */ +static inline void ccw_unit_check(struct channel *ch, unsigned char sense) +{ + CTCM_DBF_TEXT(TRACE, 5, __FUNCTION__); + if (sense & SNS0_INTERVENTION_REQ) { + if (sense & 0x01) { + ctcm_pr_debug("%s: Interface disc. or Sel. reset " + "(remote)\n", ch->id); + fsm_event(ch->fsm, CTC_EVENT_UC_RCRESET, ch); + } else { + ctcm_pr_debug("%s: System reset (remote)\n", ch->id); + fsm_event(ch->fsm, CTC_EVENT_UC_RSRESET, ch); + } + } else if (sense & SNS0_EQUIPMENT_CHECK) { + if (sense & SNS0_BUS_OUT_CHECK) { + ctcm_pr_warn("%s: Hardware malfunction (remote)\n", + ch->id); + fsm_event(ch->fsm, CTC_EVENT_UC_HWFAIL, ch); + } else { + ctcm_pr_warn("%s: Read-data parity error (remote)\n", + ch->id); + fsm_event(ch->fsm, CTC_EVENT_UC_RXPARITY, ch); + } + } else if (sense & SNS0_BUS_OUT_CHECK) { + if (sense & 0x04) { + ctcm_pr_warn("%s: Data-streaming timeout)\n", ch->id); + fsm_event(ch->fsm, CTC_EVENT_UC_TXTIMEOUT, ch); + } else { + ctcm_pr_warn("%s: Data-transfer parity error\n", + ch->id); + fsm_event(ch->fsm, CTC_EVENT_UC_TXPARITY, ch); + } + } else if (sense & SNS0_CMD_REJECT) { + ctcm_pr_warn("%s: Command reject\n", ch->id); + } else if (sense == 0) { + ctcm_pr_debug("%s: Unit check ZERO\n", ch->id); + fsm_event(ch->fsm, CTC_EVENT_UC_ZERO, ch); + } else { + ctcm_pr_warn("%s: Unit Check with sense code: %02x\n", + ch->id, sense); + fsm_event(ch->fsm, CTC_EVENT_UC_UNKNOWN, ch); + } +} + +int ctcm_ch_alloc_buffer(struct channel *ch) +{ + CTCM_DBF_TEXT(TRACE, 5, __FUNCTION__); + + clear_normalized_cda(&ch->ccw[1]); + ch->trans_skb = __dev_alloc_skb(ch->max_bufsize, GFP_ATOMIC | GFP_DMA); + if (ch->trans_skb == NULL) { + ctcm_pr_warn("%s: Couldn't alloc %s trans_skb\n", + ch->id, + (CHANNEL_DIRECTION(ch->flags) == READ) ? "RX" : "TX"); + return -ENOMEM; + } + + ch->ccw[1].count = ch->max_bufsize; + if (set_normalized_cda(&ch->ccw[1], ch->trans_skb->data)) { + dev_kfree_skb(ch->trans_skb); + ch->trans_skb = NULL; + ctcm_pr_warn("%s: set_normalized_cda for %s " + "trans_skb failed, dropping packets\n", + ch->id, + (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; +} + +/* + * 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.) + */ +int ctcm_open(struct net_device *dev) +{ + struct ctcm_priv *privptr = (struct ctcm_priv *)dev->priv; + + CTCMY_DBF_DEV_NAME(SETUP, dev, ""); + if (!IS_MPC(privptr)) + fsm_event(privptr->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.) + */ +int ctcm_close(struct net_device *dev) +{ + struct ctcm_priv *privptr = (struct ctcm_priv *)dev->priv; + + CTCMY_DBF_DEV_NAME(SETUP, dev, ""); + if (!IS_MPC(privptr)) + fsm_event(privptr->fsm, DEV_EVENT_STOP, dev); + return 0; +} + + +/** + * Transmit a packet. + * This is a helper function for ctcm_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 ctcm_tx(). + * + * @return 0 on success, -ERRNO on failure. (Never fails.) + */ +static int ctcm_transmit_skb(struct channel *ch, struct sk_buff *skb) +{ + unsigned long saveflags; + struct ll_header header; + int rc = 0; + __u16 block_len; + int ccw_idx; + struct sk_buff *nskb; + unsigned long hi; + + /* we need to acquire the lock for testing the state + * otherwise we can have an IRQ changing the state to + * TXIDLE after the test but before acquiring the lock. + */ + spin_lock_irqsave(&ch->collect_lock, saveflags); + if (fsm_getstate(ch->fsm) != CTC_STATE_TXIDLE) { + int l = skb->len + LL_HEADER_LENGTH; + + if (ch->collect_len + l > ch->max_bufsize - 2) { + spin_unlock_irqrestore(&ch->collect_lock, saveflags); + return -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); + goto done; + } + spin_unlock_irqrestore(&ch->collect_lock, saveflags); + /* + * 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; + + /* + * IDAL support in CTCM is broken, so we have to + * care about skb's above 2G ourselves. + */ + hi = ((unsigned long)skb_tail_pointer(skb) + LL_HEADER_LENGTH) >> 31; + if (hi) { + nskb = alloc_skb(skb->len, GFP_ATOMIC | GFP_DMA); + if (!nskb) { + atomic_dec(&skb->users); + skb_pull(skb, LL_HEADER_LENGTH + 2); + ctcm_clear_busy(ch->netdev); + return -ENOMEM; + } else { + memcpy(skb_put(nskb, skb->len), skb->data, skb->len); + atomic_inc(&nskb->users); + atomic_dec(&skb->users); + dev_kfree_skb_irq(skb); + skb = nskb; + } + } + + ch->ccw[4].count = block_len; + if (set_normalized_cda(&ch->ccw[4], skb->data)) { + /* + * idal allocation failed, try via copying to + * trans_skb. trans_skb usually has a pre-allocated + * idal. + */ + if (ctcm_checkalloc_buffer(ch)) { + /* + * Remove our header. It gets added + * again on retransmit. + */ + atomic_dec(&skb->users); + skb_pull(skb, LL_HEADER_LENGTH + 2); + ctcm_clear_busy(ch->netdev); + return -EBUSY; + } + + skb_reset_tail_pointer(ch->trans_skb); + ch->trans_skb->len = 0; + ch->ccw[1].count = skb->len; + skb_copy_from_linear_data(skb, + skb_put(ch->trans_skb, skb->len), 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; + fsm_newstate(ch->fsm, CTC_STATE_TX); + fsm_addtimer(&ch->timer, CTCM_TIME_5_SEC, CTC_EVENT_TIMER, ch); + spin_lock_irqsave(get_ccwdev_lock(ch->cdev), saveflags); + ch->prof.send_stamp = current_kernel_time(); /* xtime */ + rc = ccw_device_start(ch->cdev, &ch->ccw[ccw_idx], + (unsigned long)ch, 0xff, 0); + spin_unlock_irqrestore(get_ccwdev_lock(ch->cdev), saveflags); + if (ccw_idx == 3) + ch->prof.doios_single++; + if (rc != 0) { + fsm_deltimer(&ch->timer); + ctcm_ccw_check_rc(ch, rc, "single skb TX"); + 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) { + struct net_device *dev = ch->netdev; + struct ctcm_priv *privptr = dev->priv; + privptr->stats.tx_packets++; + privptr->stats.tx_bytes += skb->len - LL_HEADER_LENGTH; + } +done: + ctcm_clear_busy(ch->netdev); + return rc; +} + +static void ctcmpc_send_sweep_req(struct channel *rch) +{ + struct net_device *dev = rch->netdev; + struct ctcm_priv *privptr; + struct mpc_group *grpptr; + struct th_sweep *header; + struct sk_buff *sweep_skb; + struct channel *ch; + int rc = 0; + + privptr = (struct ctcm_priv *)dev->priv; + grpptr = privptr->mpcg; + ch = privptr->channel[WRITE]; + + if (do_debug) + MPC_DBF_DEV_NAME(TRACE, dev, ch->id); + + /* sweep processing is not complete until response and request */ + /* has completed for all read channels in group */ + if (grpptr->in_sweep == 0) { + grpptr->in_sweep = 1; + grpptr->sweep_rsp_pend_num = grpptr->active_channels[READ]; + grpptr->sweep_req_pend_num = grpptr->active_channels[READ]; + } + + sweep_skb = __dev_alloc_skb(MPC_BUFSIZE_DEFAULT, GFP_ATOMIC|GFP_DMA); + + if (sweep_skb == NULL) { + printk(KERN_INFO "Couldn't alloc sweep_skb\n"); + rc = -ENOMEM; + goto done; + } + + header = kmalloc(TH_SWEEP_LENGTH, gfp_type()); + + if (!header) { + dev_kfree_skb_any(sweep_skb); + rc = -ENOMEM; + goto done; + } + + header->th.th_seg = 0x00 ; + header->th.th_ch_flag = TH_SWEEP_REQ; /* 0x0f */ + header->th.th_blk_flag = 0x00; + header->th.th_is_xid = 0x00; + header->th.th_seq_num = 0x00; + header->sw.th_last_seq = ch->th_seq_num; + + memcpy(skb_put(sweep_skb, TH_SWEEP_LENGTH), header, TH_SWEEP_LENGTH); + + kfree(header); + + dev->trans_start = jiffies; + skb_queue_tail(&ch->sweep_queue, sweep_skb); + + fsm_addtimer(&ch->sweep_timer, 100, CTC_EVENT_RSWEEP_TIMER, ch); + + return; + +done: + if (rc != 0) { + grpptr->in_sweep = 0; + ctcm_clear_busy(dev); + fsm_event(grpptr->fsm, MPCG_EVENT_INOP, dev); + } + + return; +} + +/* + * MPC mode version of transmit_skb + */ +static int ctcmpc_transmit_skb(struct channel *ch, struct sk_buff *skb) +{ + struct pdu *p_header; + struct net_device *dev = ch->netdev; + struct ctcm_priv *privptr = (struct ctcm_priv *)dev->priv; + struct mpc_group *grpptr = privptr->mpcg; + struct th_header *header; + struct sk_buff *nskb; + int rc = 0; + int ccw_idx; + unsigned long hi; + unsigned long saveflags = 0; /* avoids compiler warning */ + __u16 block_len; + + if (do_debug) + ctcm_pr_debug( + "ctcm enter: %s(): %s cp=%i ch=0x%p id=%s state=%s\n", + __FUNCTION__, dev->name, smp_processor_id(), ch, + ch->id, fsm_getstate_str(ch->fsm)); + + if ((fsm_getstate(ch->fsm) != CTC_STATE_TXIDLE) || grpptr->in_sweep) { + spin_lock_irqsave(&ch->collect_lock, saveflags); + atomic_inc(&skb->users); + p_header = kmalloc(PDU_HEADER_LENGTH, gfp_type()); + + if (!p_header) { + printk(KERN_WARNING "ctcm: OUT OF MEMORY IN %s():" + " Data Lost \n", __FUNCTION__); + + atomic_dec(&skb->users); + dev_kfree_skb_any(skb); + spin_unlock_irqrestore(&ch->collect_lock, saveflags); + fsm_event(privptr->mpcg->fsm, MPCG_EVENT_INOP, dev); + goto done; + } + + p_header->pdu_offset = skb->len; + p_header->pdu_proto = 0x01; + p_header->pdu_flag = 0x00; + if (skb->protocol == ntohs(ETH_P_SNAP)) { + p_header->pdu_flag |= PDU_FIRST | PDU_CNTL; + } else { + p_header->pdu_flag |= PDU_FIRST; + } + p_header->pdu_seq = 0; + memcpy(skb_push(skb, PDU_HEADER_LENGTH), p_header, + PDU_HEADER_LENGTH); + + if (do_debug_data) { + ctcm_pr_debug("ctcm: %s() Putting on collect_q" + " - skb len: %04x \n", __FUNCTION__, skb->len); + ctcm_pr_debug("ctcm: %s() pdu header and data" + " for up to 32 bytes\n", __FUNCTION__); + ctcmpc_dump32((char *)skb->data, skb->len); + } + + skb_queue_tail(&ch->collect_queue, skb); + ch->collect_len += skb->len; + kfree(p_header); + + spin_unlock_irqrestore(&ch->collect_lock, saveflags); + goto done; + } + + /* + * Protect skb against beeing free'd by upper + * layers. + */ + atomic_inc(&skb->users); + + block_len = skb->len + TH_HEADER_LENGTH + PDU_HEADER_LENGTH; + /* + * IDAL support in CTCM is broken, so we have to + * care about skb's above 2G ourselves. + */ + hi = ((unsigned long)skb->tail + TH_HEADER_LENGTH) >> 31; + if (hi) { + nskb = __dev_alloc_skb(skb->len, GFP_ATOMIC | GFP_DMA); + if (!nskb) { + printk(KERN_WARNING "ctcm: %s() OUT OF MEMORY" + "- Data Lost \n", __FUNCTION__); + atomic_dec(&skb->users); + dev_kfree_skb_any(skb); + fsm_event(privptr->mpcg->fsm, MPCG_EVENT_INOP, dev); + goto done; + } else { + memcpy(skb_put(nskb, skb->len), skb->data, skb->len); + atomic_inc(&nskb->users); + atomic_dec(&skb->users); + dev_kfree_skb_irq(skb); + skb = nskb; + } + } + + p_header = kmalloc(PDU_HEADER_LENGTH, gfp_type()); + + if (!p_header) { + printk(KERN_WARNING "ctcm: %s() OUT OF MEMORY" + ": Data Lost \n", __FUNCTION__); + + atomic_dec(&skb->users); + dev_kfree_skb_any(skb); + fsm_event(privptr->mpcg->fsm, MPCG_EVENT_INOP, dev); + goto done; + } + + p_header->pdu_offset = skb->len; + p_header->pdu_proto = 0x01; + p_header->pdu_flag = 0x00; + p_header->pdu_seq = 0; + if (skb->protocol == ntohs(ETH_P_SNAP)) { + p_header->pdu_flag |= PDU_FIRST | PDU_CNTL; + } else { + p_header->pdu_flag |= PDU_FIRST; + } + memcpy(skb_push(skb, PDU_HEADER_LENGTH), p_header, PDU_HEADER_LENGTH); + + kfree(p_header); + + if (ch->collect_len > 0) { + spin_lock_irqsave(&ch->collect_lock, saveflags); + skb_queue_tail(&ch->collect_queue, skb); + ch->collect_len += skb->len; + skb = skb_dequeue(&ch->collect_queue); + ch->collect_len -= skb->len; + spin_unlock_irqrestore(&ch->collect_lock, saveflags); + } + + p_header = (struct pdu *)skb->data; + p_header->pdu_flag |= PDU_LAST; + + ch->prof.txlen += skb->len - PDU_HEADER_LENGTH; + + header = kmalloc(TH_HEADER_LENGTH, gfp_type()); + + if (!header) { + printk(KERN_WARNING "ctcm: %s() OUT OF MEMORY: Data Lost \n", + __FUNCTION__); + atomic_dec(&skb->users); + dev_kfree_skb_any(skb); + fsm_event(privptr->mpcg->fsm, MPCG_EVENT_INOP, dev); + goto done; + } + + header->th_seg = 0x00; + header->th_ch_flag = TH_HAS_PDU; /* Normal data */ + header->th_blk_flag = 0x00; + header->th_is_xid = 0x00; /* Just data here */ + ch->th_seq_num++; + header->th_seq_num = ch->th_seq_num; + + if (do_debug_data) + ctcm_pr_debug("ctcm: %s() ToVTAM_th_seq= %08x\n" , + __FUNCTION__, ch->th_seq_num); + + /* put the TH on the packet */ + memcpy(skb_push(skb, TH_HEADER_LENGTH), header, TH_HEADER_LENGTH); + + kfree(header); + + if (do_debug_data) { + ctcm_pr_debug("ctcm: %s(): skb len: %04x \n", + __FUNCTION__, skb->len); + ctcm_pr_debug("ctcm: %s(): pdu header and data for up to 32 " + "bytes sent to vtam\n", __FUNCTION__); + ctcmpc_dump32((char *)skb->data, skb->len); + } + + ch->ccw[4].count = skb->len; + if (set_normalized_cda(&ch->ccw[4], skb->data)) { + /* + * idal allocation failed, try via copying to + * trans_skb. trans_skb usually has a pre-allocated + * idal. + */ + if (ctcm_checkalloc_buffer(ch)) { + /* + * Remove our header. It gets added + * again on retransmit. + */ + atomic_dec(&skb->users); + dev_kfree_skb_any(skb); + printk(KERN_WARNING "ctcm: %s()OUT OF MEMORY:" + " Data Lost \n", __FUNCTION__); + fsm_event(privptr->mpcg->fsm, MPCG_EVENT_INOP, dev); + goto done; + } + + skb_reset_tail_pointer(ch->trans_skb); + 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; + if (do_debug_data) { + ctcm_pr_debug("ctcm: %s() TRANS skb len: %d \n", + __FUNCTION__, ch->trans_skb->len); + ctcm_pr_debug("ctcm: %s up to 32 bytes of data" + " sent to vtam\n", __FUNCTION__); + ctcmpc_dump32((char *)ch->trans_skb->data, + ch->trans_skb->len); + } + } else { + skb_queue_tail(&ch->io_queue, skb); + ccw_idx = 3; + } + ch->retry = 0; + fsm_newstate(ch->fsm, CTC_STATE_TX); + fsm_addtimer(&ch->timer, CTCM_TIME_5_SEC, CTC_EVENT_TIMER, ch); + + if (do_debug_ccw) + ctcmpc_dumpit((char *)&ch->ccw[ccw_idx], + sizeof(struct ccw1) * 3); + + spin_lock_irqsave(get_ccwdev_lock(ch->cdev), saveflags); + ch->prof.send_stamp = current_kernel_time(); /* xtime */ + rc = ccw_device_start(ch->cdev, &ch->ccw[ccw_idx], + (unsigned long)ch, 0xff, 0); + spin_unlock_irqrestore(get_ccwdev_lock(ch->cdev), saveflags); + if (ccw_idx == 3) + ch->prof.doios_single++; + if (rc != 0) { + fsm_deltimer(&ch->timer); + ctcm_ccw_check_rc(ch, rc, "single skb TX"); + if (ccw_idx == 3) + skb_dequeue_tail(&ch->io_queue); + } else if (ccw_idx == 0) { + privptr->stats.tx_packets++; + privptr->stats.tx_bytes += skb->len - TH_HEADER_LENGTH; + } + if (ch->th_seq_num > 0xf0000000) /* Chose 4Billion at random. */ + ctcmpc_send_sweep_req(ch); + +done: + if (do_debug) + ctcm_pr_debug("ctcm exit: %s %s()\n", dev->name, __FUNCTION__); + 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. + */ +/* first merge version - leaving both functions separated */ +static int ctcm_tx(struct sk_buff *skb, struct net_device *dev) +{ + int rc = 0; + struct ctcm_priv *privptr; + + CTCM_DBF_TEXT(TRACE, 5, __FUNCTION__); + privptr = (struct ctcm_priv *)dev->priv; + + if (skb == NULL) { + ctcm_pr_warn("%s: NULL sk_buff passed\n", dev->name); + privptr->stats.tx_dropped++; + return 0; + } + if (skb_headroom(skb) < (LL_HEADER_LENGTH + 2)) { + ctcm_pr_warn("%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 + * and throw away packet. + */ + if (fsm_getstate(privptr->fsm) != DEV_STATE_RUNNING) { + fsm_event(privptr->fsm, DEV_EVENT_START, dev); + dev_kfree_skb(skb); + privptr->stats.tx_dropped++; + privptr->stats.tx_errors++; + privptr->stats.tx_carrier_errors++; + return 0; + } + + if (ctcm_test_and_set_busy(dev)) + return -EBUSY; + + dev->trans_start = jiffies; + if (ctcm_transmit_skb(privptr->channel[WRITE], skb) != 0) + rc = 1; + return rc; +} + +/* unmerged MPC variant of ctcm_tx */ +static int ctcmpc_tx(struct sk_buff *skb, struct net_device *dev) +{ + int len = 0; + struct ctcm_priv *privptr = NULL; + struct mpc_group *grpptr = NULL; + struct sk_buff *newskb = NULL; + + if (do_debug) + ctcm_pr_debug("ctcmpc enter: %s(): skb:%0lx\n", + __FUNCTION__, (unsigned long)skb); + + CTCM_DBF_TEXT_(MPC_TRACE, CTC_DBF_DEBUG, + "ctcmpc enter: %s(): skb:%0lx\n", + __FUNCTION__, (unsigned long)skb); + + privptr = (struct ctcm_priv *)dev->priv; + grpptr = privptr->mpcg; + /* + * Some sanity checks ... + */ + if (skb == NULL) { + ctcm_pr_warn("ctcmpc: %s: NULL sk_buff passed\n", dev->name); + privptr->stats.tx_dropped++; + goto done; + } + if (skb_headroom(skb) < (TH_HEADER_LENGTH + PDU_HEADER_LENGTH)) { + CTCM_DBF_TEXT_(MPC_TRACE, CTC_DBF_WARN, + "%s: Got sk_buff with head room < %ld bytes\n", + dev->name, TH_HEADER_LENGTH + PDU_HEADER_LENGTH); + + if (do_debug_data) + ctcmpc_dump32((char *)skb->data, skb->len); + + len = skb->len + TH_HEADER_LENGTH + PDU_HEADER_LENGTH; + newskb = __dev_alloc_skb(len, gfp_type() | GFP_DMA); + + if (!newskb) { + printk(KERN_WARNING "ctcmpc: %s() OUT OF MEMORY-" + "Data Lost\n", + __FUNCTION__); + + dev_kfree_skb_any(skb); + privptr->stats.tx_dropped++; + privptr->stats.tx_errors++; + privptr->stats.tx_carrier_errors++; + fsm_event(grpptr->fsm, MPCG_EVENT_INOP, dev); + goto done; + } + newskb->protocol = skb->protocol; + skb_reserve(newskb, TH_HEADER_LENGTH + PDU_HEADER_LENGTH); + memcpy(skb_put(newskb, skb->len), skb->data, skb->len); + dev_kfree_skb_any(skb); + skb = newskb; + } + + /* + * If channels are not running, + * notify anybody about a link failure and throw + * away packet. + */ + if ((fsm_getstate(privptr->fsm) != DEV_STATE_RUNNING) || + (fsm_getstate(grpptr->fsm) < MPCG_STATE_XID2INITW)) { + dev_kfree_skb_any(skb); + printk(KERN_INFO "ctcmpc: %s() DATA RCVD - MPC GROUP " + "NOT ACTIVE - DROPPED\n", + __FUNCTION__); + privptr->stats.tx_dropped++; + privptr->stats.tx_errors++; + privptr->stats.tx_carrier_errors++; + goto done; + } + + if (ctcm_test_and_set_busy(dev)) { + printk(KERN_WARNING "%s:DEVICE ERR - UNRECOVERABLE DATA LOSS\n", + __FUNCTION__); + dev_kfree_skb_any(skb); + privptr->stats.tx_dropped++; + privptr->stats.tx_errors++; + privptr->stats.tx_carrier_errors++; + fsm_event(grpptr->fsm, MPCG_EVENT_INOP, dev); + goto done; + } + + dev->trans_start = jiffies; + if (ctcmpc_transmit_skb(privptr->channel[WRITE], skb) != 0) { + printk(KERN_WARNING "ctcmpc: %s() DEVICE ERROR" + ": Data Lost \n", + __FUNCTION__); + printk(KERN_WARNING "ctcmpc: %s() DEVICE ERROR" + " - UNRECOVERABLE DATA LOSS\n", + __FUNCTION__); + dev_kfree_skb_any(skb); + privptr->stats.tx_dropped++; + privptr->stats.tx_errors++; + privptr->stats.tx_carrier_errors++; + ctcm_clear_busy(dev); + fsm_event(grpptr->fsm, MPCG_EVENT_INOP, dev); + goto done; + } + ctcm_clear_busy(dev); +done: + if (do_debug) + MPC_DBF_DEV_NAME(TRACE, dev, "exit"); + + return 0; /* handle freeing of skb here */ +} + + +/** + * 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 ctcm_change_mtu(struct net_device *dev, int new_mtu) +{ + struct ctcm_priv *privptr; + int max_bufsize; + + CTCM_DBF_TEXT(SETUP, CTC_DBF_INFO, __FUNCTION__); + + if (new_mtu < 576 || new_mtu > 65527) + return -EINVAL; + + privptr = (struct ctcm_priv *)dev->priv; + max_bufsize = privptr->channel[READ]->max_bufsize; + + if (IS_MPC(privptr)) { + if (new_mtu > max_bufsize - TH_HEADER_LENGTH) + return -EINVAL; + dev->hard_header_len = TH_HEADER_LENGTH + PDU_HEADER_LENGTH; + } else { + if (new_mtu > max_bufsize - LL_HEADER_LENGTH - 2) + return -EINVAL; + dev->hard_header_len = LL_HEADER_LENGTH + 2; + } + dev->mtu = new_mtu; + 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 *ctcm_stats(struct net_device *dev) +{ + return &((struct ctcm_priv *)dev->priv)->stats; +} + + +static void ctcm_netdev_unregister(struct net_device *dev) +{ + CTCM_DBF_TEXT(SETUP, CTC_DBF_INFO, __FUNCTION__); + if (!dev) + return; + unregister_netdev(dev); +} + +static int ctcm_netdev_register(struct net_device *dev) +{ + CTCM_DBF_TEXT(SETUP, CTC_DBF_INFO, __FUNCTION__); + return register_netdev(dev); +} + +static void ctcm_free_netdevice(struct net_device *dev) +{ + struct ctcm_priv *privptr; + struct mpc_group *grpptr; + + CTCM_DBF_TEXT(SETUP, CTC_DBF_INFO, __FUNCTION__); + + if (!dev) + return; + privptr = dev->priv; + if (privptr) { + grpptr = privptr->mpcg; + if (grpptr) { + if (grpptr->fsm) + kfree_fsm(grpptr->fsm); + if (grpptr->xid_skb) + dev_kfree_skb(grpptr->xid_skb); + if (grpptr->rcvd_xid_skb) + dev_kfree_skb(grpptr->rcvd_xid_skb); + tasklet_kill(&grpptr->mpc_tasklet2); + kfree(grpptr); + privptr->mpcg = NULL; + } + if (privptr->fsm) { + kfree_fsm(privptr->fsm); + privptr->fsm = NULL; + } + kfree(privptr->xid); + privptr->xid = NULL; + /* + * Note: kfree(privptr); is done in "opposite" function of + * allocator function probe_device which is remove_device. + */ + } +#ifdef MODULE + free_netdev(dev); +#endif +} + +struct mpc_group *ctcmpc_init_mpc_group(struct ctcm_priv *privptr); + +void static ctcm_dev_setup(struct net_device *dev) +{ + dev->open = ctcm_open; + dev->stop = ctcm_close; + dev->get_stats = ctcm_stats; + dev->change_mtu = ctcm_change_mtu; + dev->type = ARPHRD_SLIP; + dev->tx_queue_len = 100; + dev->flags = IFF_POINTOPOINT | IFF_NOARP; +} + +/* + * Initialize everything of the net device except the name and the + * channel structs. + */ +static struct net_device *ctcm_init_netdevice(struct ctcm_priv *privptr) +{ + struct net_device *dev; + struct mpc_group *grpptr; + if (!privptr) + return NULL; + + if (IS_MPC(privptr)) + dev = alloc_netdev(0, MPC_DEVICE_GENE, ctcm_dev_setup); + else + dev = alloc_netdev(0, CTC_DEVICE_GENE, ctcm_dev_setup); + + if (!dev) { + ctcm_pr_err("%s: Out of memory\n", __FUNCTION__); + return NULL; + } + dev->priv = privptr; + privptr->fsm = init_fsm("ctcmdev", dev_state_names, dev_event_names, + CTCM_NR_DEV_STATES, CTCM_NR_DEV_EVENTS, + dev_fsm, dev_fsm_len, GFP_KERNEL); + if (privptr->fsm == NULL) { + CTCMY_DBF_DEV(SETUP, dev, "init_fsm error"); + kfree(dev); + return NULL; + } + fsm_newstate(privptr->fsm, DEV_STATE_STOPPED); + fsm_settimer(privptr->fsm, &privptr->restart_timer); + + if (IS_MPC(privptr)) { + /* MPC Group Initializations */ + grpptr = ctcmpc_init_mpc_group(privptr); + if (grpptr == NULL) { + MPC_DBF_DEV(SETUP, dev, "init_mpc_group error"); + kfree(dev); + return NULL; + } + tasklet_init(&grpptr->mpc_tasklet2, + mpc_group_ready, (unsigned long)dev); + dev->mtu = MPC_BUFSIZE_DEFAULT - + TH_HEADER_LENGTH - PDU_HEADER_LENGTH; + + dev->hard_start_xmit = ctcmpc_tx; + dev->hard_header_len = TH_HEADER_LENGTH + PDU_HEADER_LENGTH; + privptr->buffer_size = MPC_BUFSIZE_DEFAULT; + } else { + dev->mtu = CTCM_BUFSIZE_DEFAULT - LL_HEADER_LENGTH - 2; + dev->hard_start_xmit = ctcm_tx; + dev->hard_header_len = LL_HEADER_LENGTH + 2; + } + + CTCMY_DBF_DEV(SETUP, dev, "finished"); + return dev; +} + +/** + * Main IRQ handler. + * + * @param cdev The ccw_device the interrupt is for. + * @param intparm interruption parameter. + * @param irb interruption response block. + */ +static void ctcm_irq_handler(struct ccw_device *cdev, + unsigned long intparm, struct irb *irb) +{ + struct channel *ch; + struct net_device *dev; + struct ctcm_priv *priv; + struct ccwgroup_device *cgdev; + + CTCM_DBF_TEXT(TRACE, CTC_DBF_DEBUG, __FUNCTION__); + if (ctcm_check_irb_error(cdev, irb)) + return; + + cgdev = dev_get_drvdata(&cdev->dev); + + /* Check for unsolicited interrupts. */ + if (cgdev == NULL) { + ctcm_pr_warn("ctcm: Got unsolicited irq: %s c-%02x d-%02x\n", + cdev->dev.bus_id, irb->scsw.cstat, + irb->scsw.dstat); + return; + } + + priv = dev_get_drvdata(&cgdev->dev); + + /* Try to extract channel from driver data. */ + if (priv->channel[READ]->cdev == cdev) + ch = priv->channel[READ]; + else if (priv->channel[WRITE]->cdev == cdev) + ch = priv->channel[WRITE]; + else { + ctcm_pr_err("ctcm: Can't determine channel for interrupt, " + "device %s\n", cdev->dev.bus_id); + return; + } + + dev = (struct net_device *)(ch->netdev); + if (dev == NULL) { + ctcm_pr_crit("ctcm: %s dev=NULL bus_id=%s, ch=0x%p\n", + __FUNCTION__, cdev->dev.bus_id, ch); + return; + } + + if (do_debug) + ctcm_pr_debug("%s: interrupt for device: %s " + "received c-%02x d-%02x\n", + dev->name, + ch->id, + irb->scsw.cstat, + irb->scsw.dstat); + + /* Copy interruption response block. */ + memcpy(ch->irb, irb, sizeof(struct irb)); + + /* Check for good subchannel return code, otherwise error message */ + if (irb->scsw.cstat) { + fsm_event(ch->fsm, CTC_EVENT_SC_UNKNOWN, ch); + ctcm_pr_warn("%s: subchannel check for dev: %s - %02x %02x\n", + dev->name, ch->id, irb->scsw.cstat, + irb->scsw.dstat); + return; + } + + /* Check the reason-code of a unit check */ + if (irb->scsw.dstat & DEV_STAT_UNIT_CHECK) { + ccw_unit_check(ch, irb->ecw[0]); + return; + } + if (irb->scsw.dstat & DEV_STAT_BUSY) { + if (irb->scsw.dstat & DEV_STAT_ATTENTION) + fsm_event(ch->fsm, CTC_EVENT_ATTNBUSY, ch); + else + fsm_event(ch->fsm, CTC_EVENT_BUSY, ch); + return; + } + if (irb->scsw.dstat & DEV_STAT_ATTENTION) { + fsm_event(ch->fsm, CTC_EVENT_ATTN, ch); + return; + } + if ((irb->scsw.stctl & SCSW_STCTL_SEC_STATUS) || + (irb->scsw.stctl == SCSW_STCTL_STATUS_PEND) || + (irb->scsw.stctl == + (SCSW_STCTL_ALERT_STATUS | SCSW_STCTL_STATUS_PEND))) + fsm_event(ch->fsm, CTC_EVENT_FINSTAT, ch); + else + fsm_event(ch->fsm, CTC_EVENT_IRQ, ch); + +} + +/** + * Add ctcm specific attributes. + * Add ctcm private data. + * + * @param cgdev pointer to ccwgroup_device just added + * + * @returns 0 on success, !0 on failure. + */ +static int ctcm_probe_device(struct ccwgroup_device *cgdev) +{ + struct ctcm_priv *priv; + int rc; + + CTCM_DBF_TEXT_(SETUP, CTC_DBF_INFO, "%s %p", __FUNCTION__, cgdev); + + if (!get_device(&cgdev->dev)) + return -ENODEV; + + priv = kzalloc(sizeof(struct ctcm_priv), GFP_KERNEL); + if (!priv) { + ctcm_pr_err("%s: Out of memory\n", __FUNCTION__); + put_device(&cgdev->dev); + return -ENOMEM; + } + + rc = ctcm_add_files(&cgdev->dev); + if (rc) { + kfree(priv); + put_device(&cgdev->dev); + return rc; + } + priv->buffer_size = CTCM_BUFSIZE_DEFAULT; + cgdev->cdev[0]->handler = ctcm_irq_handler; + cgdev->cdev[1]->handler = ctcm_irq_handler; + dev_set_drvdata(&cgdev->dev, priv); + + return 0; +} + +/** + * Add a new channel to the list of channels. + * Keeps the channel list sorted. + * + * @param cdev The ccw_device to be added. + * @param type The type class of the new channel. + * @param priv Points to the private data of the ccwgroup_device. + * + * @return 0 on success, !0 on error. + */ +static int add_channel(struct ccw_device *cdev, enum channel_types type, + struct ctcm_priv *priv) +{ + struct channel **c = &channels; + struct channel *ch; + int ccw_num; + int rc = 0; + + CTCM_DBF_TEXT(TRACE, 2, __FUNCTION__); + ch = kzalloc(sizeof(struct channel), GFP_KERNEL); + if (ch == NULL) + goto nomem_return; + + ch->protocol = priv->protocol; + if (IS_MPC(priv)) { + ch->discontact_th = (struct th_header *) + kzalloc(TH_HEADER_LENGTH, gfp_type()); + if (ch->discontact_th == NULL) + goto nomem_return; + + ch->discontact_th->th_blk_flag = TH_DISCONTACT; + tasklet_init(&ch->ch_disc_tasklet, + mpc_action_send_discontact, (unsigned long)ch); + + tasklet_init(&ch->ch_tasklet, ctcmpc_bh, (unsigned long)ch); + ch->max_bufsize = (MPC_BUFSIZE_DEFAULT - 35); + ccw_num = 17; + } else + ccw_num = 8; + + ch->ccw = (struct ccw1 *) + kzalloc(ccw_num * sizeof(struct ccw1), GFP_KERNEL | GFP_DMA); + if (ch->ccw == NULL) + goto nomem_return; + + ch->cdev = cdev; + snprintf(ch->id, CTCM_ID_SIZE, "ch-%s", cdev->dev.bus_id); + ch->type = type; + + /** + * "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): + * 6: set extended mode + * 7: nop + * + * ch->ccw[0..5] are initialized in ch_action_start because + * the channel's direction is yet unknown here. + * + * ccws used for xid2 negotiations + * ch-ccw[8-14] need to be used for the XID exchange either + * X side XID2 Processing + * 8: write control + * 9: write th + * 10: write XID + * 11: read th from secondary + * 12: read XID from secondary + * 13: read 4 byte ID + * 14: nop + * Y side XID Processing + * 8: sense + * 9: read th + * 10: read XID + * 11: write th + * 12: write XID + * 13: write 4 byte ID + * 14: nop + * + * ccws used for double noop due to VM timing issues + * which result in unrecoverable Busy on channel + * 15: nop + * 16: nop + */ + ch->ccw[6].cmd_code = CCW_CMD_SET_EXTENDED; + ch->ccw[6].flags = CCW_FLAG_SLI; + + ch->ccw[7].cmd_code = CCW_CMD_NOOP; + ch->ccw[7].flags = CCW_FLAG_SLI; + + if (IS_MPC(priv)) { + ch->ccw[15].cmd_code = CCW_CMD_WRITE; + ch->ccw[15].flags = CCW_FLAG_SLI | CCW_FLAG_CC; + ch->ccw[15].count = TH_HEADER_LENGTH; + ch->ccw[15].cda = virt_to_phys(ch->discontact_th); + + ch->ccw[16].cmd_code = CCW_CMD_NOOP; + ch->ccw[16].flags = CCW_FLAG_SLI; + + ch->fsm = init_fsm(ch->id, ctc_ch_state_names, + ctc_ch_event_names, CTC_MPC_NR_STATES, + CTC_MPC_NR_EVENTS, ctcmpc_ch_fsm, + mpc_ch_fsm_len, GFP_KERNEL); + } else { + ch->fsm = init_fsm(ch->id, ctc_ch_state_names, + ctc_ch_event_names, CTC_NR_STATES, + CTC_NR_EVENTS, ch_fsm, + ch_fsm_len, GFP_KERNEL); + } + if (ch->fsm == NULL) + goto free_return; + + fsm_newstate(ch->fsm, CTC_STATE_IDLE); + + ch->irb = kzalloc(sizeof(struct irb), GFP_KERNEL); + if (ch->irb == NULL) + goto nomem_return; + + while (*c && ctcm_less_than((*c)->id, ch->id)) + c = &(*c)->next; + + if (*c && (!strncmp((*c)->id, ch->id, CTCM_ID_SIZE))) { + CTCM_DBF_TEXT_(SETUP, CTC_DBF_INFO, + "%s (%s) already in list, using old entry", + __FUNCTION__, (*c)->id); + + goto free_return; + } + + spin_lock_init(&ch->collect_lock); + + fsm_settimer(ch->fsm, &ch->timer); + skb_queue_head_init(&ch->io_queue); + skb_queue_head_init(&ch->collect_queue); + + if (IS_MPC(priv)) { + fsm_settimer(ch->fsm, &ch->sweep_timer); + skb_queue_head_init(&ch->sweep_queue); + } + ch->next = *c; + *c = ch; + return 0; + +nomem_return: + ctcm_pr_warn("ctcm: Out of memory in %s\n", __FUNCTION__); + rc = -ENOMEM; + +free_return: /* note that all channel pointers are 0 or valid */ + kfree(ch->ccw); /* TODO: check that again */ + kfree(ch->discontact_th); + kfree_fsm(ch->fsm); + kfree(ch->irb); + kfree(ch); + return rc; +} + +/* + * Return type of a detected device. + */ +static enum channel_types get_channel_type(struct ccw_device_id *id) +{ + enum channel_types type; + type = (enum channel_types)id->driver_info; + + if (type == channel_type_ficon) + type = channel_type_escon; + + return type; +} + +/** + * + * Setup an interface. + * + * @param cgdev Device to be setup. + * + * @returns 0 on success, !0 on failure. + */ +static int ctcm_new_device(struct ccwgroup_device *cgdev) +{ + char read_id[CTCM_ID_SIZE]; + char write_id[CTCM_ID_SIZE]; + int direction; + enum channel_types type; + struct ctcm_priv *privptr; + struct net_device *dev; + int ret; + + CTCM_DBF_TEXT(SETUP, CTC_DBF_INFO, __FUNCTION__); + + privptr = dev_get_drvdata(&cgdev->dev); + if (!privptr) + return -ENODEV; + + type = get_channel_type(&cgdev->cdev[0]->id); + + snprintf(read_id, CTCM_ID_SIZE, "ch-%s", cgdev->cdev[0]->dev.bus_id); + snprintf(write_id, CTCM_ID_SIZE, "ch-%s", cgdev->cdev[1]->dev.bus_id); + + ret = add_channel(cgdev->cdev[0], type, privptr); + if (ret) + return ret; + ret = add_channel(cgdev->cdev[1], type, privptr); + if (ret) + return ret; + + ret = ccw_device_set_online(cgdev->cdev[0]); + if (ret != 0) { + CTCM_DBF_TEXT(SETUP, CTC_DBF_WARN, + "ccw_device_set_online (cdev[0]) failed "); + ctcm_pr_warn("ccw_device_set_online (cdev[0]) failed " + "with ret = %d\n", ret); + } + + ret = ccw_device_set_online(cgdev->cdev[1]); + if (ret != 0) { + CTCM_DBF_TEXT(SETUP, CTC_DBF_WARN, + "ccw_device_set_online (cdev[1]) failed "); + ctcm_pr_warn("ccw_device_set_online (cdev[1]) failed " + "with ret = %d\n", ret); + } + + dev = ctcm_init_netdevice(privptr); + + if (dev == NULL) { + ctcm_pr_warn("ctcm_init_netdevice failed\n"); + goto out; + } + + for (direction = READ; direction <= WRITE; direction++) { + privptr->channel[direction] = + channel_get(type, direction == READ ? read_id : write_id, + direction); + if (privptr->channel[direction] == NULL) { + if (direction == WRITE) + channel_free(privptr->channel[READ]); + ctcm_free_netdevice(dev); + goto out; + } + privptr->channel[direction]->netdev = dev; + privptr->channel[direction]->protocol = privptr->protocol; + privptr->channel[direction]->max_bufsize = privptr->buffer_size; + } + /* sysfs magic */ + SET_NETDEV_DEV(dev, &cgdev->dev); + + if (ctcm_netdev_register(dev) != 0) { + ctcm_free_netdevice(dev); + goto out; + } + + if (ctcm_add_attributes(&cgdev->dev)) { + ctcm_netdev_unregister(dev); +/* dev->priv = NULL; why that ???? */ + ctcm_free_netdevice(dev); + goto out; + } + + strlcpy(privptr->fsm->name, dev->name, sizeof(privptr->fsm->name)); + + CTCM_DBF_TEXT_(SETUP, CTC_DBF_INFO, + "setup(%s) ok : r/w = %s / %s, proto : %d", + dev->name, privptr->channel[READ]->id, + privptr->channel[WRITE]->id, privptr->protocol); + + return 0; +out: + ccw_device_set_offline(cgdev->cdev[1]); + ccw_device_set_offline(cgdev->cdev[0]); + + return -ENODEV; +} + +/** + * Shutdown an interface. + * + * @param cgdev Device to be shut down. + * + * @returns 0 on success, !0 on failure. + */ +static int ctcm_shutdown_device(struct ccwgroup_device *cgdev) +{ + struct ctcm_priv *priv; + struct net_device *dev; + + priv = dev_get_drvdata(&cgdev->dev); + if (!priv) + return -ENODEV; + + if (priv->channel[READ]) { + dev = priv->channel[READ]->netdev; + CTCM_DBF_DEV(SETUP, dev, ""); + /* Close the device */ + ctcm_close(dev); + dev->flags &= ~IFF_RUNNING; + ctcm_remove_attributes(&cgdev->dev); + channel_free(priv->channel[READ]); + } else + dev = NULL; + + if (priv->channel[WRITE]) + channel_free(priv->channel[WRITE]); + + if (dev) { + ctcm_netdev_unregister(dev); +/* dev->priv = NULL; why that ??? */ + ctcm_free_netdevice(dev); + } + + if (priv->fsm) + kfree_fsm(priv->fsm); + + ccw_device_set_offline(cgdev->cdev[1]); + ccw_device_set_offline(cgdev->cdev[0]); + + if (priv->channel[READ]) + channel_remove(priv->channel[READ]); + if (priv->channel[WRITE]) + channel_remove(priv->channel[WRITE]); + priv->channel[READ] = priv->channel[WRITE] = NULL; + + return 0; + +} + + +static void ctcm_remove_device(struct ccwgroup_device *cgdev) +{ + struct ctcm_priv *priv; + + CTCM_DBF_TEXT(SETUP, CTC_DBF_ERROR, __FUNCTION__); + + priv = dev_get_drvdata(&cgdev->dev); + if (!priv) + return; + if (cgdev->state == CCWGROUP_ONLINE) + ctcm_shutdown_device(cgdev); + ctcm_remove_files(&cgdev->dev); + dev_set_drvdata(&cgdev->dev, NULL); + kfree(priv); + put_device(&cgdev->dev); +} + +static struct ccwgroup_driver ctcm_group_driver = { + .owner = THIS_MODULE, + .name = CTC_DRIVER_NAME, + .max_slaves = 2, + .driver_id = 0xC3E3C3D4, /* CTCM */ + .probe = ctcm_probe_device, + .remove = ctcm_remove_device, + .set_online = ctcm_new_device, + .set_offline = ctcm_shutdown_device, +}; + + +/* + * Module related routines + */ + +/* + * 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. + */ +static void __exit ctcm_exit(void) +{ + unregister_cu3088_discipline(&ctcm_group_driver); + ctcm_unregister_dbf_views(); + ctcm_pr_info("CTCM driver unloaded\n"); +} + +/* + * Print Banner. + */ +static void print_banner(void) +{ + printk(KERN_INFO "CTCM driver initialized\n"); +} + +/** + * Initialize module. + * This is called just after the module is loaded. + * + * @return 0 on success, !0 on error. + */ +static int __init ctcm_init(void) +{ + int ret; + + channels = NULL; + + ret = ctcm_register_dbf_views(); + if (ret) { + ctcm_pr_crit("ctcm_init failed with ctcm_register_dbf_views " + "rc = %d\n", ret); + return ret; + } + ret = register_cu3088_discipline(&ctcm_group_driver); + if (ret) { + ctcm_unregister_dbf_views(); + ctcm_pr_crit("ctcm_init failed with register_cu3088_discipline " + "(rc = %d)\n", ret); + return ret; + } + print_banner(); + return ret; +} + +module_init(ctcm_init); +module_exit(ctcm_exit); + +MODULE_AUTHOR("Peter Tiedemann <ptiedem@de.ibm.com>"); +MODULE_DESCRIPTION("Network driver for S/390 CTC + CTCMPC (SNA)"); +MODULE_LICENSE("GPL"); + Index: linux-2.6-uschi/drivers/s390/net/ctcm_main.h =================================================================== --- /dev/null +++ linux-2.6-uschi/drivers/s390/net/ctcm_main.h @@ -0,0 +1,287 @@ +/* + * drivers/s390/net/ctcm_main.h + * + * Copyright IBM Corp. 2001, 2007 + * Authors: Fritz Elfert (felfert@millenux.com) + * Peter Tiedemann (ptiedem@de.ibm.com) + */ + +#ifndef _CTCM_MAIN_H_ +#define _CTCM_MAIN_H_ + +#include <asm/ccwdev.h> +#include <asm/ccwgroup.h> + +#include <linux/skbuff.h> +#include <linux/netdevice.h> + +#include "fsm.h" +#include "cu3088.h" +#include "ctcm_dbug.h" +#include "ctcm_mpc.h" + +#define CTC_DRIVER_NAME "ctcm" +#define CTC_DEVICE_NAME "ctc" +#define CTC_DEVICE_GENE "ctc%d" +#define MPC_DEVICE_NAME "mpc" +#define MPC_DEVICE_GENE "mpc%d" + +#define CHANNEL_FLAGS_READ 0 +#define CHANNEL_FLAGS_WRITE 1 +#define CHANNEL_FLAGS_INUSE 2 +#define CHANNEL_FLAGS_BUFSIZE_CHANGED 4 +#define CHANNEL_FLAGS_FAILED 8 +#define CHANNEL_FLAGS_WAITIRQ 16 +#define CHANNEL_FLAGS_RWMASK 1 +#define CHANNEL_DIRECTION(f) (f & CHANNEL_FLAGS_RWMASK) + +#define LOG_FLAG_ILLEGALPKT 1 +#define LOG_FLAG_ILLEGALSIZE 2 +#define LOG_FLAG_OVERRUN 4 +#define LOG_FLAG_NOMEM 8 + +#define ctcm_pr_debug(fmt, arg...) printk(KERN_DEBUG fmt, ##arg) +#define ctcm_pr_info(fmt, arg...) printk(KERN_INFO fmt, ##arg) +#define ctcm_pr_notice(fmt, arg...) printk(KERN_NOTICE fmt, ##arg) +#define ctcm_pr_warn(fmt, arg...) printk(KERN_WARNING fmt, ##arg) +#define ctcm_pr_emerg(fmt, arg...) printk(KERN_EMERG fmt, ##arg) +#define ctcm_pr_err(fmt, arg...) printk(KERN_ERR fmt, ##arg) +#define ctcm_pr_crit(fmt, arg...) printk(KERN_CRIT fmt, ##arg) + +/* + * CCW commands, used in this driver. + */ +#define CCW_CMD_WRITE 0x01 +#define CCW_CMD_READ 0x02 +#define CCW_CMD_NOOP 0x03 +#define CCW_CMD_TIC 0x08 +#define CCW_CMD_SENSE_CMD 0x14 +#define CCW_CMD_WRITE_CTL 0x17 +#define CCW_CMD_SET_EXTENDED 0xc3 +#define CCW_CMD_PREPARE 0xe3 + +#define CTCM_PROTO_S390 0 +#define CTCM_PROTO_LINUX 1 +#define CTCM_PROTO_LINUX_TTY 2 +#define CTCM_PROTO_OS390 3 +#define CTCM_PROTO_MPC 4 +#define CTCM_PROTO_MAX 4 + +#define CTCM_BUFSIZE_LIMIT 65535 +#define CTCM_BUFSIZE_DEFAULT 32768 +#define MPC_BUFSIZE_DEFAULT CTCM_BUFSIZE_LIMIT + +#define CTCM_TIME_1_SEC 1000 +#define CTCM_TIME_5_SEC 5000 +#define CTCM_TIME_10_SEC 10000 + +#define CTCM_INITIAL_BLOCKLEN 2 + +#define READ 0 +#define WRITE 1 + +#define CTCM_ID_SIZE BUS_ID_SIZE+3 + +struct ctcm_profile { + unsigned long maxmulti; + unsigned long maxcqueue; + unsigned long doios_single; + unsigned long doios_multi; + unsigned long txlen; + unsigned long tx_time; + struct timespec send_stamp; +}; + +/* + * Definition of one channel + */ +struct channel { + struct channel *next; + char id[CTCM_ID_SIZE]; + struct ccw_device *cdev; + /* + * Type of this channel. + * CTC/A or Escon for valid channels. + */ + enum channel_types type; + /* + * Misc. flags. See CHANNEL_FLAGS_... below + */ + __u32 flags; + __u16 protocol; /* protocol of this channel (4 = MPC) */ + /* + * I/O and irq related stuff + */ + struct ccw1 *ccw; + struct irb *irb; + /* + * RX/TX buffer size + */ + int max_bufsize; + struct sk_buff *trans_skb; /* transmit/receive buffer */ + struct sk_buff_head io_queue; /* universal I/O queue */ + struct tasklet_struct ch_tasklet; /* MPC ONLY */ + /* + * 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; + /* MPC ONLY section begin */ + __u32 th_seq_num; /* SNA TH seq number */ + __u8 th_seg; + __u32 pdu_seq; + struct sk_buff *xid_skb; + char *xid_skb_data; + struct th_header *xid_th; + struct xid2 *xid; + char *xid_id; + struct th_header *rcvd_xid_th; + struct xid2 *rcvd_xid; + char *rcvd_xid_id; + __u8 in_mpcgroup; + fsm_timer sweep_timer; + struct sk_buff_head sweep_queue; + struct th_header *discontact_th; + struct tasklet_struct ch_disc_tasklet; + /* MPC ONLY section end */ + + int retry; /* retry counter for misc. operations */ + fsm_instance *fsm; /* finite state machine of this channel */ + struct net_device *netdev; /* corresponding net_device */ + struct ctcm_profile prof; + unsigned char *trans_skb_data; + __u16 logflags; +}; + +struct ctcm_priv { + struct net_device_stats stats; + unsigned long tbusy; + + /* The MPC group struct of this interface */ + struct mpc_group *mpcg; /* MPC only */ + struct xid2 *xid; /* MPC only */ + + /* The finite state machine of this interface */ + fsm_instance *fsm; + + /* The protocol of this device */ + __u16 protocol; + + /* Timer for restarting after I/O Errors */ + fsm_timer restart_timer; + + int buffer_size; /* ctc only */ + + struct channel *channel[2]; +}; + +int ctcm_open(struct net_device *dev); +int ctcm_close(struct net_device *dev); + +/* + * prototypes for non-static sysfs functions + */ +int ctcm_add_attributes(struct device *dev); +void ctcm_remove_attributes(struct device *dev); +int ctcm_add_files(struct device *dev); +void ctcm_remove_files(struct device *dev); + +/* + * Compatibility macros for busy handling + * of network devices. + */ +static inline void ctcm_clear_busy_do(struct net_device *dev) +{ + clear_bit(0, &(((struct ctcm_priv *)dev->priv)->tbusy)); + netif_wake_queue(dev); +} + +static inline void ctcm_clear_busy(struct net_device *dev) +{ + struct mpc_group *grpptr; + grpptr = ((struct ctcm_priv *)dev->priv)->mpcg; + + if (!(grpptr && grpptr->in_sweep)) + ctcm_clear_busy_do(dev); +} + + +static inline int ctcm_test_and_set_busy(struct net_device *dev) +{ + netif_stop_queue(dev); + return test_and_set_bit(0, &(((struct ctcm_priv *)dev->priv)->tbusy)); +} + +extern int loglevel; +extern struct channel *channels; + +void ctcm_unpack_skb(struct channel *ch, struct sk_buff *pskb); + +/* + * Functions related to setup and device detection. + */ + +static inline int ctcm_less_than(char *id1, char *id2) +{ + unsigned long dev1, dev2; + + id1 = id1 + 5; + id2 = id2 + 5; + + dev1 = simple_strtoul(id1, &id1, 16); + dev2 = simple_strtoul(id2, &id2, 16); + + return (dev1 < dev2); +} + +int ctcm_ch_alloc_buffer(struct channel *ch); + +static inline int ctcm_checkalloc_buffer(struct channel *ch) +{ + if (ch->trans_skb == NULL) + return ctcm_ch_alloc_buffer(ch); + if (ch->flags & CHANNEL_FLAGS_BUFSIZE_CHANGED) { + dev_kfree_skb(ch->trans_skb); + return ctcm_ch_alloc_buffer(ch); + } + return 0; +} + +struct mpc_group *ctcmpc_init_mpc_group(struct ctcm_priv *privptr); + +/* test if protocol attribute (of struct ctcm_priv or struct channel) + * has MPC protocol setting. Type is not checked + */ +#define IS_MPC(p) ((p)->protocol == CTCM_PROTO_MPC) + +/* test if struct ctcm_priv of struct net_device has MPC protocol setting */ +#define IS_MPCDEV(d) IS_MPC((struct ctcm_priv *)d->priv) + +static inline gfp_t gfp_type(void) +{ + return in_interrupt() ? GFP_ATOMIC : GFP_KERNEL; +} + +/* + * Definition of our link level header. + */ +struct ll_header { + __u16 length; + __u16 type; + __u16 unused; +}; +#define LL_HEADER_LENGTH (sizeof(struct ll_header)) + +#endif Index: linux-2.6-uschi/drivers/s390/net/ctcm_mpc.c =================================================================== --- /dev/null +++ linux-2.6-uschi/drivers/s390/net/ctcm_mpc.c @@ -0,0 +1,2467 @@ +/* + * drivers/s390/net/ctcm_mpc.c + * + * Copyright IBM Corp. 2004, 2007 + * Authors: Belinda Thompson (belindat@us.ibm.com) + * Andy Richter (richtera@us.ibm.com) + * Peter Tiedemann (ptiedem@de.ibm.com) + */ + +/* + This module exports functions to be used by CCS: + EXPORT_SYMBOL(ctc_mpc_alloc_channel); + EXPORT_SYMBOL(ctc_mpc_establish_connectivity); + EXPORT_SYMBOL(ctc_mpc_dealloc_ch); + EXPORT_SYMBOL(ctc_mpc_flow_control); +*/ + +#undef DEBUG +#undef DEBUGDATA +#undef DEBUGCCW + +#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 <linux/netdevice.h> +#include <net/dst.h> + +#include <linux/io.h> /* instead of <asm/io.h> ok ? */ +#include <asm/ccwdev.h> +#include <asm/ccwgroup.h> +#include <linux/bitops.h> /* instead of <asm/bitops.h> ok ? */ +#include <linux/uaccess.h> /* instead of <asm/uaccess.h> ok ? */ +#include <linux/wait.h> +#include <linux/moduleparam.h> +#include <asm/idals.h> + +#include "cu3088.h" +#include "ctcm_mpc.h" +#include "ctcm_main.h" +#include "ctcm_fsms.h" + +static const struct xid2 init_xid = { + .xid2_type_id = XID_FM2, + .xid2_len = 0x45, + .xid2_adj_id = 0, + .xid2_rlen = 0x31, + .xid2_resv1 = 0, + .xid2_flag1 = 0, + .xid2_fmtt = 0, + .xid2_flag4 = 0x80, + .xid2_resv2 = 0, + .xid2_tgnum = 0, + .xid2_sender_id = 0, + .xid2_flag2 = 0, + .xid2_option = XID2_0, + .xid2_resv3 = "\x00", + .xid2_resv4 = 0, + .xid2_dlc_type = XID2_READ_SIDE, + .xid2_resv5 = 0, + .xid2_mpc_flag = 0, + .xid2_resv6 = 0, + .xid2_buf_len = (MPC_BUFSIZE_DEFAULT - 35), +}; + +static const struct th_header thnorm = { + .th_seg = 0x00, + .th_ch_flag = TH_IS_XID, + .th_blk_flag = TH_DATA_IS_XID, + .th_is_xid = 0x01, + .th_seq_num = 0x00000000, +}; + +static const struct th_header thdummy = { + .th_seg = 0x00, + .th_ch_flag = 0x00, + .th_blk_flag = TH_DATA_IS_XID, + .th_is_xid = 0x01, + .th_seq_num = 0x00000000, +}; + +/* + * Definition of one MPC group + */ + +/* + * Compatibility macros for busy handling + * of network devices. + */ + +static void ctcmpc_unpack_skb(struct channel *ch, struct sk_buff *pskb); + +/* + * MPC Group state machine actions (static prototypes) + */ +static void mpc_action_go_ready(fsm_instance *fsm, int event, void *arg); +static void mpc_action_go_inop(fsm_instance *fi, int event, void *arg); +static void mpc_action_timeout(fsm_instance *fi, int event, void *arg); +static int mpc_validate_xid(struct mpcg_info *mpcginfo); +static void mpc_action_yside_xid(fsm_instance *fsm, int event, void *arg); +static void mpc_action_doxid0(fsm_instance *fsm, int event, void *arg); +static void mpc_action_doxid7(fsm_instance *fsm, int event, void *arg); +static void mpc_action_xside_xid(fsm_instance *fsm, int event, void *arg); +static void mpc_action_rcvd_xid0(fsm_instance *fsm, int event, void *arg); +static void mpc_action_rcvd_xid7(fsm_instance *fsm, int event, void *arg); + +#ifdef DEBUGDATA +/*-------------------------------------------------------------------* +* Dump buffer format * +* * +*--------------------------------------------------------------------*/ +void ctcmpc_dumpit(char *buf, int len) +{ + __u32 ct, sw, rm, dup; + char *ptr, *rptr; + char tbuf[82], tdup[82]; + #if (UTS_MACHINE == s390x) + char addr[22]; + #else + char addr[12]; + #endif + char boff[12]; + char bhex[82], duphex[82]; + char basc[40]; + + sw = 0; + rptr = ptr = buf; + rm = 16; + duphex[0] = 0x00; + dup = 0; + + for (ct = 0; ct < len; ct++, ptr++, rptr++) { + if (sw == 0) { + #if (UTS_MACHINE == s390x) + sprintf(addr, "%16.16lx", (unsigned long)rptr); + #else + sprintf(addr, "%8.8X", (__u32)rptr); + #endif + + sprintf(boff, "%4.4X", (__u32)ct); + bhex[0] = '\0'; + basc[0] = '\0'; + } + if ((sw == 4) || (sw == 12)) + strcat(bhex, " "); + if (sw == 8) + strcat(bhex, " "); + + #if (UTS_MACHINE == s390x) + sprintf(tbuf, "%2.2lX", (unsigned long)*ptr); + #else + sprintf(tbuf, "%2.2X", (__u32)*ptr); + #endif + + tbuf[2] = '\0'; + strcat(bhex, tbuf); + if ((0 != isprint(*ptr)) && (*ptr >= 0x20)) + basc[sw] = *ptr; + else + basc[sw] = '.'; + + basc[sw+1] = '\0'; + sw++; + rm--; + if (sw == 16) { + if ((strcmp(duphex, bhex)) != 0) { + if (dup != 0) { + sprintf(tdup, "Duplicate as above " + "to %s", addr); + printk(KERN_INFO " " + " --- %s ---\n", tdup); + } + printk(KERN_INFO " %s (+%s) : %s [%s]\n", + addr, boff, bhex, basc); + dup = 0; + strcpy(duphex, bhex); + } else + dup++; + + sw = 0; + rm = 16; + } + } /* endfor */ + + if (sw != 0) { + for ( ; rm > 0; rm--, sw++) { + if ((sw == 4) || (sw == 12)) + strcat(bhex, " "); + if (sw == 8) + strcat(bhex, " "); + strcat(bhex, " "); + strcat(basc, " "); + } + if (dup != 0) { + sprintf(tdup, "Duplicate as above to %s", addr); + printk(KERN_INFO " " + " --- %s ---\n", tdup); + } + printk(KERN_INFO " %s (+%s) : %s [%s]\n", + addr, boff, bhex, basc); + } else { + if (dup >= 1) { + sprintf(tdup, "Duplicate as above to %s", addr); + printk(KERN_INFO " " + " --- %s ---\n", tdup); + } + if (dup != 0) { + printk(KERN_INFO " %s (+%s) : %s [%s]\n", + addr, boff, bhex, basc); + } + } + + return; + +} /* end of ctcmpc_dumpit */ +#endif + +#ifdef DEBUGDATA +/* + * 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. + */ +void ctcmpc_dump_skb(struct sk_buff *skb, int offset) +{ + unsigned char *p = skb->data; + struct th_header *header; + struct pdu *pheader; + int bl = skb->len; + int i; + + if (p == NULL) + return; + + p += offset; + header = (struct th_header *)p; + + printk(KERN_INFO "dump:\n"); + printk(KERN_INFO "skb len=%d \n", skb->len); + if (skb->len > 2) { + switch (header->th_ch_flag) { + case TH_HAS_PDU: + break; + case 0x00: + case TH_IS_XID: + if ((header->th_blk_flag == TH_DATA_IS_XID) && + (header->th_is_xid == 0x01)) + goto dumpth; + case TH_SWEEP_REQ: + goto dumpth; + case TH_SWEEP_RESP: + goto dumpth; + default: + break; + } + + pheader = (struct pdu *)p; + printk(KERN_INFO "pdu->offset: %d hex: %04x\n", + pheader->pdu_offset, pheader->pdu_offset); + printk(KERN_INFO "pdu->flag : %02x\n", pheader->pdu_flag); + printk(KERN_INFO "pdu->proto : %02x\n", pheader->pdu_proto); + printk(KERN_INFO "pdu->seq : %02x\n", pheader->pdu_seq); + goto dumpdata; + +dumpth: + printk(KERN_INFO "th->seg : %02x\n", header->th_seg); + printk(KERN_INFO "th->ch : %02x\n", header->th_ch_flag); + printk(KERN_INFO "th->blk_flag: %02x\n", header->th_blk_flag); + printk(KERN_INFO "th->type : %s\n", + (header->th_is_xid) ? "DATA" : "XID"); + printk(KERN_INFO "th->seqnum : %04x\n", header->th_seq_num); + + } +dumpdata: + if (bl > 32) + bl = 32; + printk(KERN_INFO "data: "); + for (i = 0; i < bl; i++) + printk(KERN_INFO "%02x%s", *p++, (i % 16) ? " " : "\n<7>"); + printk(KERN_INFO "\n"); +} +#endif + +/* + * ctc_mpc_alloc_channel + * (exported interface) + * + * Device Initialization : + * ACTPATH driven IO operations + */ +int ctc_mpc_alloc_channel(int port_num, void (*callback)(int, int)) +{ + char device[20]; + struct net_device *dev; + struct mpc_group *grpptr; + struct ctcm_priv *privptr; + + ctcm_pr_debug("ctcmpc enter: %s()\n", __FUNCTION__); + + sprintf(device, "%s%i", MPC_DEVICE_NAME, port_num); + dev = __dev_get_by_name(&init_net, device); + + if (dev == NULL) { + printk(KERN_INFO "ctc_mpc_alloc_channel %s dev=NULL\n", device); + return 1; + } + + privptr = (struct ctcm_priv *)dev->priv; + grpptr = privptr->mpcg; + if (!grpptr) + return 1; + + grpptr->allochanfunc = callback; + grpptr->port_num = port_num; + grpptr->port_persist = 1; + + ctcm_pr_debug("ctcmpc: %s called for device %s state=%s\n", + __FUNCTION__, + dev->name, + fsm_getstate_str(grpptr->fsm)); + + switch (fsm_getstate(grpptr->fsm)) { + case MPCG_STATE_INOP: + /* Group is in the process of terminating */ + grpptr->alloc_called = 1; + break; + case MPCG_STATE_RESET: + /* MPC Group will transition to state */ + /* MPCG_STATE_XID2INITW iff the minimum number */ + /* of 1 read and 1 write channel have successfully*/ + /* activated */ + /*fsm_newstate(grpptr->fsm, MPCG_STATE_XID2INITW);*/ + if (callback) + grpptr->send_qllc_disc = 1; + case MPCG_STATE_XID0IOWAIT: + fsm_deltimer(&grpptr->timer); + grpptr->outstanding_xid2 = 0; + grpptr->outstanding_xid7 = 0; + grpptr->outstanding_xid7_p2 = 0; + grpptr->saved_xid2 = NULL; + if (callback) + ctcm_open(dev); + fsm_event(((struct ctcm_priv *)dev->priv)->fsm, + DEV_EVENT_START, dev); + break; + case MPCG_STATE_READY: + /* XID exchanges completed after PORT was activated */ + /* Link station already active */ + /* Maybe timing issue...retry callback */ + grpptr->allocchan_callback_retries++; + if (grpptr->allocchan_callback_retries < 4) { + if (grpptr->allochanfunc) + grpptr->allochanfunc(grpptr->port_num, + grpptr->group_max_buflen); + } else { + /* there are problems...bail out */ + /* there may be a state mismatch so restart */ + grpptr->port_persist = 1; + fsm_event(grpptr->fsm, MPCG_EVENT_INOP, dev); + grpptr->allocchan_callback_retries = 0; + } + break; + default: + return 0; + + } + + ctcm_pr_debug("ctcmpc exit: %s()\n", __FUNCTION__); + return 0; +} +EXPORT_SYMBOL(ctc_mpc_alloc_channel); + +/* + * ctc_mpc_establish_connectivity + * (exported interface) + */ +void ctc_mpc_establish_connectivity(int port_num, + void (*callback)(int, int, int)) +{ + char device[20]; + struct net_device *dev; + struct mpc_group *grpptr; + struct ctcm_priv *privptr; + struct channel *rch, *wch; + + ctcm_pr_debug("ctcmpc enter: %s()\n", __FUNCTION__); + + sprintf(device, "%s%i", MPC_DEVICE_NAME, port_num); + dev = __dev_get_by_name(&init_net, device); + + if (dev == NULL) { + printk(KERN_INFO "ctc_mpc_establish_connectivity " + "%s dev=NULL\n", device); + return; + } + privptr = (struct ctcm_priv *)dev->priv; + rch = privptr->channel[READ]; + wch = privptr->channel[WRITE]; + + grpptr = privptr->mpcg; + + ctcm_pr_debug("ctcmpc: %s() called for device %s state=%s\n", + __FUNCTION__, dev->name, + fsm_getstate_str(grpptr->fsm)); + + grpptr->estconnfunc = callback; + grpptr->port_num = port_num; + + switch (fsm_getstate(grpptr->fsm)) { + case MPCG_STATE_READY: + /* XID exchanges completed after PORT was activated */ + /* Link station already active */ + /* Maybe timing issue...retry callback */ + fsm_deltimer(&grpptr->timer); + grpptr->estconn_callback_retries++; + if (grpptr->estconn_callback_retries < 4) { + if (grpptr->estconnfunc) { + grpptr->estconnfunc(grpptr->port_num, 0, + grpptr->group_max_buflen); + grpptr->estconnfunc = NULL; + } + } else { + /* there are problems...bail out */ + fsm_event(grpptr->fsm, MPCG_EVENT_INOP, dev); + grpptr->estconn_callback_retries = 0; + } + break; + case MPCG_STATE_INOP: + case MPCG_STATE_RESET: + /* MPC Group is not ready to start XID - min num of */ + /* 1 read and 1 write channel have not been acquired*/ + printk(KERN_WARNING "ctcmpc: %s() REJECTED ACTIVE XID Req" + "uest - Channel Pair is not Active\n", __FUNCTION__); + if (grpptr->estconnfunc) { + grpptr->estconnfunc(grpptr->port_num, -1, 0); + grpptr->estconnfunc = NULL; + } + break; + case MPCG_STATE_XID2INITW: + /* alloc channel was called but no XID exchange */ + /* has occurred. initiate xside XID exchange */ + /* make sure yside XID0 processing has not started */ + if ((fsm_getstate(rch->fsm) > CH_XID0_PENDING) || + (fsm_getstate(wch->fsm) > CH_XID0_PENDING)) { + printk(KERN_WARNING "mpc: %s() ABORT ACTIVE XID" + " Request- PASSIVE XID in process\n" + , __FUNCTION__); + break; + } + grpptr->send_qllc_disc = 1; + fsm_newstate(grpptr->fsm, MPCG_STATE_XID0IOWAIT); + fsm_deltimer(&grpptr->timer); + fsm_addtimer(&grpptr->timer, MPC_XID_TIMEOUT_VALUE, + MPCG_EVENT_TIMER, dev); + grpptr->outstanding_xid7 = 0; + grpptr->outstanding_xid7_p2 = 0; + grpptr->saved_xid2 = NULL; + if ((rch->in_mpcgroup) && + (fsm_getstate(rch->fsm) == CH_XID0_PENDING)) + fsm_event(grpptr->fsm, MPCG_EVENT_XID0DO, rch); + else { + printk(KERN_WARNING "mpc: %s() Unable to start" + " ACTIVE XID0 on read channel\n", + __FUNCTION__); + if (grpptr->estconnfunc) { + grpptr->estconnfunc(grpptr->port_num, -1, 0); + grpptr->estconnfunc = NULL; + } + fsm_deltimer(&grpptr->timer); + goto done; + } + if ((wch->in_mpcgroup) && + (fsm_getstate(wch->fsm) == CH_XID0_PENDING)) + fsm_event(grpptr->fsm, MPCG_EVENT_XID0DO, wch); + else { + printk(KERN_WARNING "mpc: %s() Unable to start" + " ACTIVE XID0 on write channel\n", + __FUNCTION__); + if (grpptr->estconnfunc) { + grpptr->estconnfunc(grpptr->port_num, -1, 0); + grpptr->estconnfunc = NULL; + } + fsm_deltimer(&grpptr->timer); + goto done; + } + break; + case MPCG_STATE_XID0IOWAIT: + /* already in active XID negotiations */ + default: + break; + } + +done: + ctcm_pr_debug("ctcmpc exit: %s()\n", __FUNCTION__); + return; +} +EXPORT_SYMBOL(ctc_mpc_establish_connectivity); + +/* + * ctc_mpc_dealloc_ch + * (exported interface) + */ +void ctc_mpc_dealloc_ch(int port_num) +{ + struct net_device *dev; + char device[20]; + struct ctcm_priv *privptr; + struct mpc_group *grpptr; + + ctcm_pr_debug("ctcmpc enter: %s()\n", __FUNCTION__); + sprintf(device, "%s%i", MPC_DEVICE_NAME, port_num); + dev = __dev_get_by_name(&init_net, device); + + if (dev == NULL) { + printk(KERN_INFO "%s() %s dev=NULL\n", __FUNCTION__, device); + goto done; + } + + ctcm_pr_debug("ctcmpc:%s %s() called for device %s refcount=%d\n", + dev->name, __FUNCTION__, + dev->name, atomic_read(&dev->refcnt)); + + privptr = (struct ctcm_priv *)dev->priv; + if (privptr == NULL) { + printk(KERN_INFO "%s() %s privptr=NULL\n", + __FUNCTION__, device); + goto done; + } + fsm_deltimer(&privptr->restart_timer); + + grpptr = privptr->mpcg; + if (grpptr == NULL) { + printk(KERN_INFO "%s() %s dev=NULL\n", __FUNCTION__, device); + goto done; + } + grpptr->channels_terminating = 0; + + fsm_deltimer(&grpptr->timer); + + grpptr->allochanfunc = NULL; + grpptr->estconnfunc = NULL; + grpptr->port_persist = 0; + grpptr->send_qllc_disc = 0; + fsm_event(grpptr->fsm, MPCG_EVENT_INOP, dev); + + ctcm_close(dev); +done: + ctcm_pr_debug("ctcmpc exit: %s()\n", __FUNCTION__); + return; +} +EXPORT_SYMBOL(ctc_mpc_dealloc_ch); + +/* + * ctc_mpc_flow_control + * (exported interface) + */ +void ctc_mpc_flow_control(int port_num, int flowc) +{ + char device[20]; + struct ctcm_priv *privptr; + struct mpc_group *grpptr; + struct net_device *dev; + struct channel *rch; + int mpcg_state; + + ctcm_pr_debug("ctcmpc enter: %s() %i\n", __FUNCTION__, flowc); + + sprintf(device, "%s%i", MPC_DEVICE_NAME, port_num); + dev = __dev_get_by_name(&init_net, device); + + if (dev == NULL) { + printk(KERN_INFO "ctc_mpc_flow_control %s dev=NULL\n", device); + return; + } + + ctcm_pr_debug("ctcmpc: %s %s called \n", dev->name, __FUNCTION__); + + privptr = (struct ctcm_priv *)dev->priv; + if (privptr == NULL) { + printk(KERN_INFO "ctcmpc:%s() %s privptr=NULL\n", + __FUNCTION__, device); + return; + } + grpptr = privptr->mpcg; + rch = privptr->channel[READ]; + + mpcg_state = fsm_getstate(grpptr->fsm); + switch (flowc) { + case 1: + if (mpcg_state == MPCG_STATE_FLOWC) + break; + if (mpcg_state == MPCG_STATE_READY) { + if (grpptr->flow_off_called == 1) + grpptr->flow_off_called = 0; + else + fsm_newstate(grpptr->fsm, MPCG_STATE_FLOWC); + break; + } + break; + case 0: + if (mpcg_state == MPCG_STATE_FLOWC) { + fsm_newstate(grpptr->fsm, MPCG_STATE_READY); + /* ensure any data that has accumulated */ + /* on the io_queue will now be sen t */ + tasklet_schedule(&rch->ch_tasklet); + } + /* possible race condition */ + if (mpcg_state == MPCG_STATE_READY) { + grpptr->flow_off_called = 1; + break; + } + break; + } + + ctcm_pr_debug("ctcmpc exit: %s() %i\n", __FUNCTION__, flowc); +} +EXPORT_SYMBOL(ctc_mpc_flow_control); + +static int mpc_send_qllc_discontact(struct net_device *); + +/* + * helper function of ctcmpc_unpack_skb +*/ +static void mpc_rcvd_sweep_resp(struct mpcg_info *mpcginfo) +{ + struct channel *rch = mpcginfo->ch; + struct net_device *dev = rch->netdev; + struct ctcm_priv *privptr = (struct ctcm_priv *)dev->priv; + struct mpc_group *grpptr = privptr->mpcg; + struct channel *ch = privptr->channel[WRITE]; + + if (do_debug) + ctcm_pr_debug("ctcmpc enter: %s(): ch=0x%p id=%s\n", + __FUNCTION__, ch, ch->id); + + if (do_debug_data) + ctcmpc_dumpit((char *)mpcginfo->sweep, TH_SWEEP_LENGTH); + + grpptr->sweep_rsp_pend_num--; + + if ((grpptr->sweep_req_pend_num == 0) && + (grpptr->sweep_rsp_pend_num == 0)) { + fsm_deltimer(&ch->sweep_timer); + grpptr->in_sweep = 0; + rch->th_seq_num = 0x00; + ch->th_seq_num = 0x00; + ctcm_clear_busy_do(dev); + } + + kfree(mpcginfo); + + return; + +} + +/* + * helper function of mpc_rcvd_sweep_req + * which is a helper of ctcmpc_unpack_skb + */ +static void ctcmpc_send_sweep_resp(struct channel *rch) +{ + struct net_device *dev = rch->netdev; + struct ctcm_priv *privptr = (struct ctcm_priv *)dev->priv; + struct mpc_group *grpptr = privptr->mpcg; + int rc = 0; + struct th_sweep *header; + struct sk_buff *sweep_skb; + struct channel *ch = privptr->channel[WRITE]; + + if (do_debug) + ctcm_pr_debug("ctcmpc exit : %s(): ch=0x%p id=%s\n", + __FUNCTION__, rch, rch->id); + + sweep_skb = __dev_alloc_skb(MPC_BUFSIZE_DEFAULT, + GFP_ATOMIC|GFP_DMA); + if (sweep_skb == NULL) { + printk(KERN_INFO "Couldn't alloc sweep_skb\n"); + rc = -ENOMEM; + goto done; + } + + header = (struct th_sweep *) + kmalloc(sizeof(struct th_sweep), gfp_type()); + + if (!header) { + dev_kfree_skb_any(sweep_skb); + rc = -ENOMEM; + goto done; + } + + header->th.th_seg = 0x00 ; + header->th.th_ch_flag = TH_SWEEP_RESP; + header->th.th_blk_flag = 0x00; + header->th.th_is_xid = 0x00; + header->th.th_seq_num = 0x00; + header->sw.th_last_seq = ch->th_seq_num; + + memcpy(skb_put(sweep_skb, TH_SWEEP_LENGTH), header, TH_SWEEP_LENGTH); + + kfree(header); + + dev->trans_start = jiffies; + skb_queue_tail(&ch->sweep_queue, sweep_skb); + + fsm_addtimer(&ch->sweep_timer, 100, CTC_EVENT_RSWEEP_TIMER, ch); + + return; + +done: + if (rc != 0) { + grpptr->in_sweep = 0; + ctcm_clear_busy_do(dev); + fsm_event(grpptr->fsm, MPCG_EVENT_INOP, dev); + } + + return; +} + +/* + * helper function of ctcmpc_unpack_skb + */ +static void mpc_rcvd_sweep_req(struct mpcg_info *mpcginfo) +{ + struct channel *rch = mpcginfo->ch; + struct net_device *dev = rch->netdev; + struct ctcm_priv *privptr = (struct ctcm_priv *)dev->priv; + struct mpc_group *grpptr = privptr->mpcg; + struct channel *ch = privptr->channel[WRITE]; + + if (do_debug) + CTCM_DBF_TEXT_(MPC_TRACE, CTC_DBF_DEBUG, + " %s(): ch=0x%p id=%s\n", __FUNCTION__, ch, ch->id); + + if (grpptr->in_sweep == 0) { + grpptr->in_sweep = 1; + ctcm_test_and_set_busy(dev); + grpptr->sweep_req_pend_num = grpptr->active_channels[READ]; + grpptr->sweep_rsp_pend_num = grpptr->active_channels[READ]; + } + + if (do_debug_data) + ctcmpc_dumpit((char *)mpcginfo->sweep, TH_SWEEP_LENGTH); + + grpptr->sweep_req_pend_num--; + ctcmpc_send_sweep_resp(ch); + kfree(mpcginfo); + return; +} + +/* + * MPC Group Station FSM definitions + */ +static const char *mpcg_event_names[] = { + [MPCG_EVENT_INOP] = "INOP Condition", + [MPCG_EVENT_DISCONC] = "Discontact Received", + [MPCG_EVENT_XID0DO] = "Channel Active - Start XID", + [MPCG_EVENT_XID2] = "XID2 Received", + [MPCG_EVENT_XID2DONE] = "XID0 Complete", + [MPCG_EVENT_XID7DONE] = "XID7 Complete", + [MPCG_EVENT_TIMER] = "XID Setup Timer", + [MPCG_EVENT_DOIO] = "XID DoIO", +}; + +static const char *mpcg_state_names[] = { + [MPCG_STATE_RESET] = "Reset", + [MPCG_STATE_INOP] = "INOP", + [MPCG_STATE_XID2INITW] = "Passive XID- XID0 Pending Start", + [MPCG_STATE_XID2INITX] = "Passive XID- XID0 Pending Complete", + [MPCG_STATE_XID7INITW] = "Passive XID- XID7 Pending P1 Start", + [MPCG_STATE_XID7INITX] = "Passive XID- XID7 Pending P2 Complete", + [MPCG_STATE_XID0IOWAIT] = "Active XID- XID0 Pending Start", + [MPCG_STATE_XID0IOWAIX] = "Active XID- XID0 Pending Complete", + [MPCG_STATE_XID7INITI] = "Active XID- XID7 Pending Start", + [MPCG_STATE_XID7INITZ] = "Active XID- XID7 Pending Complete ", + [MPCG_STATE_XID7INITF] = "XID - XID7 Complete ", + [MPCG_STATE_FLOWC] = "FLOW CONTROL ON", + [MPCG_STATE_READY] = "READY", +}; + +/* + * The MPC Group Station FSM + * 22 events + */ +static const fsm_node mpcg_fsm[] = { + { MPCG_STATE_RESET, MPCG_EVENT_INOP, mpc_action_go_inop }, + { MPCG_STATE_INOP, MPCG_EVENT_INOP, fsm_action_nop }, + { MPCG_STATE_FLOWC, MPCG_EVENT_INOP, mpc_action_go_inop }, + + { MPCG_STATE_READY, MPCG_EVENT_DISCONC, mpc_action_discontact }, + { MPCG_STATE_READY, MPCG_EVENT_INOP, mpc_action_go_inop }, + + { MPCG_STATE_XID2INITW, MPCG_EVENT_XID0DO, mpc_action_doxid0 }, + { MPCG_STATE_XID2INITW, MPCG_EVENT_XID2, mpc_action_rcvd_xid0 }, + { MPCG_STATE_XID2INITW, MPCG_EVENT_INOP, mpc_action_go_inop }, + { MPCG_STATE_XID2INITW, MPCG_EVENT_TIMER, mpc_action_timeout }, + { MPCG_STATE_XID2INITW, MPCG_EVENT_DOIO, mpc_action_yside_xid }, + + { MPCG_STATE_XID2INITX, MPCG_EVENT_XID0DO, mpc_action_doxid0 }, + { MPCG_STATE_XID2INITX, MPCG_EVENT_XID2, mpc_action_rcvd_xid0 }, + { MPCG_STATE_XID2INITX, MPCG_EVENT_INOP, mpc_action_go_inop }, + { MPCG_STATE_XID2INITX, MPCG_EVENT_TIMER, mpc_action_timeout }, + { MPCG_STATE_XID2INITX, MPCG_EVENT_DOIO, mpc_action_yside_xid }, + + { MPCG_STATE_XID7INITW, MPCG_EVENT_XID2DONE, mpc_action_doxid7 }, + { MPCG_STATE_XID7INITW, MPCG_EVENT_DISCONC, mpc_action_discontact }, + { MPCG_STATE_XID7INITW, MPCG_EVENT_XID2, mpc_action_rcvd_xid7 }, + { MPCG_STATE_XID7INITW, MPCG_EVENT_INOP, mpc_action_go_inop }, + { MPCG_STATE_XID7INITW, MPCG_EVENT_TIMER, mpc_action_timeout }, + { MPCG_STATE_XID7INITW, MPCG_EVENT_XID7DONE, mpc_action_doxid7 }, + { MPCG_STATE_XID7INITW, MPCG_EVENT_DOIO, mpc_action_yside_xid }, + + { MPCG_STATE_XID7INITX, MPCG_EVENT_DISCONC, mpc_action_discontact }, + { MPCG_STATE_XID7INITX, MPCG_EVENT_XID2, mpc_action_rcvd_xid7 }, + { MPCG_STATE_XID7INITX, MPCG_EVENT_INOP, mpc_action_go_inop }, + { MPCG_STATE_XID7INITX, MPCG_EVENT_XID7DONE, mpc_action_doxid7 }, + { MPCG_STATE_XID7INITX, MPCG_EVENT_TIMER, mpc_action_timeout }, + { MPCG_STATE_XID7INITX, MPCG_EVENT_DOIO, mpc_action_yside_xid }, + + { MPCG_STATE_XID0IOWAIT, MPCG_EVENT_XID0DO, mpc_action_doxid0 }, + { MPCG_STATE_XID0IOWAIT, MPCG_EVENT_DISCONC, mpc_action_discontact }, + { MPCG_STATE_XID0IOWAIT, MPCG_EVENT_XID2, mpc_action_rcvd_xid0 }, + { MPCG_STATE_XID0IOWAIT, MPCG_EVENT_INOP, mpc_action_go_inop }, + { MPCG_STATE_XID0IOWAIT, MPCG_EVENT_TIMER, mpc_action_timeout }, + { MPCG_STATE_XID0IOWAIT, MPCG_EVENT_DOIO, mpc_action_xside_xid }, + + { MPCG_STATE_XID0IOWAIX, MPCG_EVENT_XID0DO, mpc_action_doxid0 }, + { MPCG_STATE_XID0IOWAIX, MPCG_EVENT_DISCONC, mpc_action_discontact }, + { MPCG_STATE_XID0IOWAIX, MPCG_EVENT_XID2, mpc_action_rcvd_xid0 }, + { MPCG_STATE_XID0IOWAIX, MPCG_EVENT_INOP, mpc_action_go_inop }, + { MPCG_STATE_XID0IOWAIX, MPCG_EVENT_TIMER, mpc_action_timeout }, + { MPCG_STATE_XID0IOWAIX, MPCG_EVENT_DOIO, mpc_action_xside_xid }, + + { MPCG_STATE_XID7INITI, MPCG_EVENT_XID2DONE, mpc_action_doxid7 }, + { MPCG_STATE_XID7INITI, MPCG_EVENT_XID2, mpc_action_rcvd_xid7 }, + { MPCG_STATE_XID7INITI, MPCG_EVENT_DISCONC, mpc_action_discontact }, + { MPCG_STATE_XID7INITI, MPCG_EVENT_INOP, mpc_action_go_inop }, + { MPCG_STATE_XID7INITI, MPCG_EVENT_TIMER, mpc_action_timeout }, + { MPCG_STATE_XID7INITI, MPCG_EVENT_XID7DONE, mpc_action_doxid7 }, + { MPCG_STATE_XID7INITI, MPCG_EVENT_DOIO, mpc_action_xside_xid }, + + { MPCG_STATE_XID7INITZ, MPCG_EVENT_XID2, mpc_action_rcvd_xid7 }, + { MPCG_STATE_XID7INITZ, MPCG_EVENT_XID7DONE, mpc_action_doxid7 }, + { MPCG_STATE_XID7INITZ, MPCG_EVENT_DISCONC, mpc_action_discontact }, + { MPCG_STATE_XID7INITZ, MPCG_EVENT_INOP, mpc_action_go_inop }, + { MPCG_STATE_XID7INITZ, MPCG_EVENT_TIMER, mpc_action_timeout }, + { MPCG_STATE_XID7INITZ, MPCG_EVENT_DOIO, mpc_action_xside_xid }, + + { MPCG_STATE_XID7INITF, MPCG_EVENT_INOP, mpc_action_go_inop }, + { MPCG_STATE_XID7INITF, MPCG_EVENT_XID7DONE, mpc_action_go_ready }, +}; + +static int mpcg_fsm_len = ARRAY_SIZE(mpcg_fsm); + +/* + * MPC Group Station FSM action + * CTCM_PROTO_MPC only + */ +static void mpc_action_go_ready(fsm_instance *fsm, int event, void *arg) +{ + struct net_device *dev = (struct net_device *)arg; + struct ctcm_priv *privptr = NULL; + struct mpc_group *grpptr = NULL; + + if (dev == NULL) { + printk(KERN_INFO "%s() dev=NULL\n", __FUNCTION__); + return; + } + + ctcm_pr_debug("ctcmpc enter: %s %s()\n", dev->name, __FUNCTION__); + + privptr = (struct ctcm_priv *)dev->priv; + if (privptr == NULL) { + printk(KERN_INFO "%s() privptr=NULL\n", __FUNCTION__); + return; + } + + grpptr = privptr->mpcg; + if (grpptr == NULL) { + printk(KERN_INFO "%s() grpptr=NULL\n", __FUNCTION__); + return; + } + + fsm_deltimer(&grpptr->timer); + + if (grpptr->saved_xid2->xid2_flag2 == 0x40) { + privptr->xid->xid2_flag2 = 0x00; + if (grpptr->estconnfunc) { + grpptr->estconnfunc(grpptr->port_num, 1, + grpptr->group_max_buflen); + grpptr->estconnfunc = NULL; + } else if (grpptr->allochanfunc) + grpptr->send_qllc_disc = 1; + goto done; + } + + grpptr->port_persist = 1; + grpptr->out_of_sequence = 0; + grpptr->estconn_called = 0; + + tasklet_hi_schedule(&grpptr->mpc_tasklet2); + + ctcm_pr_debug("ctcmpc exit: %s %s()\n", dev->name, __FUNCTION__); + return; + +done: + fsm_event(grpptr->fsm, MPCG_EVENT_INOP, dev); + + + ctcm_pr_info("ctcmpc: %s()failure occurred\n", __FUNCTION__); +} + +/* + * helper of ctcm_init_netdevice + * CTCM_PROTO_MPC only + */ +void mpc_group_ready(unsigned long adev) +{ + struct net_device *dev = (struct net_device *)adev; + struct ctcm_priv *privptr = NULL; + struct mpc_group *grpptr = NULL; + struct channel *ch = NULL; + + + ctcm_pr_debug("ctcmpc enter: %s()\n", __FUNCTION__); + + if (dev == NULL) { + printk(KERN_INFO "%s() dev=NULL\n", __FUNCTION__); + return; + } + + privptr = (struct ctcm_priv *)dev->priv; + if (privptr == NULL) { + printk(KERN_INFO "%s() privptr=NULL\n", __FUNCTION__); + return; + } + + grpptr = privptr->mpcg; + if (grpptr == NULL) { + printk(KERN_INFO "ctcmpc:%s() grpptr=NULL\n", __FUNCTION__); + return; + } + + printk(KERN_NOTICE "ctcmpc: %s GROUP TRANSITIONED TO READY" + " maxbuf:%d\n", + dev->name, grpptr->group_max_buflen); + + fsm_newstate(grpptr->fsm, MPCG_STATE_READY); + + /* Put up a read on the channel */ + ch = privptr->channel[READ]; + ch->pdu_seq = 0; + if (do_debug_data) + ctcm_pr_debug("ctcmpc: %s() ToDCM_pdu_seq= %08x\n" , + __FUNCTION__, ch->pdu_seq); + + ctcmpc_chx_rxidle(ch->fsm, CTC_EVENT_START, ch); + /* Put the write channel in idle state */ + ch = privptr->channel[WRITE]; + if (ch->collect_len > 0) { + spin_lock(&ch->collect_lock); + ctcm_purge_skb_queue(&ch->collect_queue); + ch->collect_len = 0; + spin_unlock(&ch->collect_lock); + } + ctcm_chx_txidle(ch->fsm, CTC_EVENT_START, ch); + + ctcm_clear_busy(dev); + + if (grpptr->estconnfunc) { + grpptr->estconnfunc(grpptr->port_num, 0, + grpptr->group_max_buflen); + grpptr->estconnfunc = NULL; + } else + if (grpptr->allochanfunc) + grpptr->allochanfunc(grpptr->port_num, + grpptr->group_max_buflen); + + grpptr->send_qllc_disc = 1; + grpptr->changed_side = 0; + + ctcm_pr_debug("ctcmpc exit: %s()\n", __FUNCTION__); + return; + +} + +/* + * Increment the MPC Group Active Channel Counts + * helper of dev_action (called from channel fsm) + */ +int mpc_channel_action(struct channel *ch, int direction, int action) +{ + struct net_device *dev = ch->netdev; + struct ctcm_priv *privptr; + struct mpc_group *grpptr = NULL; + int rc = 0; + + if (do_debug) + ctcm_pr_debug("ctcmpc enter: %s(): ch=0x%p id=%s\n", + __FUNCTION__, ch, ch->id); + + if (dev == NULL) { + printk(KERN_INFO "ctcmpc_channel_action %i dev=NULL\n", + action); + rc = 1; + goto done; + } + + privptr = (struct ctcm_priv *)dev->priv; + if (privptr == NULL) { + printk(KERN_INFO + "ctcmpc_channel_action%i privptr=NULL, dev=%s\n", + action, dev->name); + rc = 2; + goto done; + } + + grpptr = privptr->mpcg; + + if (grpptr == NULL) { + printk(KERN_INFO "ctcmpc: %s()%i mpcgroup=NULL, dev=%s\n", + __FUNCTION__, action, dev->name); + rc = 3; + goto done; + } + + ctcm_pr_info( + "ctcmpc: %s() %i(): Grp:%s total_channel_paths=%i " + "active_channels read=%i, write=%i\n", + __FUNCTION__, + action, + fsm_getstate_str(grpptr->fsm), + grpptr->num_channel_paths, + grpptr->active_channels[READ], + grpptr->active_channels[WRITE]); + + if ((action == MPC_CHANNEL_ADD) && (ch->in_mpcgroup == 0)) { + grpptr->num_channel_paths++; + grpptr->active_channels[direction]++; + grpptr->outstanding_xid2++; + ch->in_mpcgroup = 1; + + if (ch->xid_skb != NULL) + dev_kfree_skb_any(ch->xid_skb); + + ch->xid_skb = __dev_alloc_skb(MPC_BUFSIZE_DEFAULT, + GFP_ATOMIC | GFP_DMA); + if (ch->xid_skb == NULL) { + printk(KERN_INFO "ctcmpc: %s()" + "Couldn't alloc ch xid_skb\n", __FUNCTION__); + fsm_event(grpptr->fsm, MPCG_EVENT_INOP, dev); + return 1; + } + ch->xid_skb_data = ch->xid_skb->data; + ch->xid_th = (struct th_header *)ch->xid_skb->data; + skb_put(ch->xid_skb, TH_HEADER_LENGTH); + ch->xid = (struct xid2 *)skb_tail_pointer(ch->xid_skb); + skb_put(ch->xid_skb, XID2_LENGTH); + ch->xid_id = skb_tail_pointer(ch->xid_skb); + ch->xid_skb->data = ch->xid_skb_data; + skb_reset_tail_pointer(ch->xid_skb); + ch->xid_skb->len = 0; + + memcpy(skb_put(ch->xid_skb, grpptr->xid_skb->len), + grpptr->xid_skb->data, + grpptr->xid_skb->len); + + ch->xid->xid2_dlc_type = ((CHANNEL_DIRECTION(ch->flags) == READ) + ? XID2_READ_SIDE : XID2_WRITE_SIDE); + + if (CHANNEL_DIRECTION(ch->flags) == WRITE) + ch->xid->xid2_buf_len = 0x00; + + ch->xid_skb->data = ch->xid_skb_data; + skb_reset_tail_pointer(ch->xid_skb); + ch->xid_skb->len = 0; + + fsm_newstate(ch->fsm, CH_XID0_PENDING); + + if ((grpptr->active_channels[READ] > 0) && + (grpptr->active_channels[WRITE] > 0) && + (fsm_getstate(grpptr->fsm) < MPCG_STATE_XID2INITW)) { + fsm_newstate(grpptr->fsm, MPCG_STATE_XID2INITW); + printk(KERN_NOTICE "ctcmpc: %s MPC GROUP " + "CHANNELS ACTIVE\n", dev->name); + } + } else if ((action == MPC_CHANNEL_REMOVE) && + (ch->in_mpcgroup == 1)) { + ch->in_mpcgroup = 0; + grpptr->num_channel_paths--; + grpptr->active_channels[direction]--; + + if (ch->xid_skb != NULL) + dev_kfree_skb_any(ch->xid_skb); + ch->xid_skb = NULL; + + if (grpptr->channels_terminating) + goto done; + + if (((grpptr->active_channels[READ] == 0) && + (grpptr->active_channels[WRITE] > 0)) + || ((grpptr->active_channels[WRITE] == 0) && + (grpptr->active_channels[READ] > 0))) + fsm_event(grpptr->fsm, MPCG_EVENT_INOP, dev); + } + +done: + + if (do_debug) { + ctcm_pr_debug( + "ctcmpc: %s() %i Grp:%s ttl_chan_paths=%i " + "active_chans read=%i, write=%i\n", + __FUNCTION__, + action, + fsm_getstate_str(grpptr->fsm), + grpptr->num_channel_paths, + grpptr->active_channels[READ], + grpptr->active_channels[WRITE]); + + ctcm_pr_debug("ctcmpc exit : %s(): ch=0x%p id=%s\n", + __FUNCTION__, ch, ch->id); + } + return rc; + +} + +/** + * Unpack a just received skb and hand it over to + * upper layers. + * special MPC version of unpack_skb. + * + * @param ch The channel where this skb has been received. + * @param pskb The received skb. + */ +static void ctcmpc_unpack_skb(struct channel *ch, struct sk_buff *pskb) +{ + struct net_device *dev = ch->netdev; + struct ctcm_priv *privptr = (struct ctcm_priv *)dev->priv; + struct mpc_group *grpptr = privptr->mpcg; + struct pdu *curr_pdu; + struct mpcg_info *mpcginfo; + struct th_header *header = NULL; + struct th_sweep *sweep = NULL; + int pdu_last_seen = 0; + __u32 new_len; + struct sk_buff *skb; + int skblen; + int sendrc = 0; + + if (do_debug) + ctcm_pr_debug("ctcmpc enter: %s() %s cp:%i ch:%s\n", + __FUNCTION__, dev->name, smp_processor_id(), ch->id); + + header = (struct th_header *)pskb->data; + if ((header->th_seg == 0) && + (header->th_ch_flag == 0) && + (header->th_blk_flag == 0) && + (header->th_seq_num == 0)) + /* nothing for us */ goto done; + + if (do_debug_data) { + ctcm_pr_debug("ctcmpc: %s() th_header\n", __FUNCTION__); + ctcmpc_dumpit((char *)header, TH_HEADER_LENGTH); + ctcm_pr_debug("ctcmpc: %s() pskb len: %04x \n", + __FUNCTION__, pskb->len); + } + + pskb->dev = dev; + pskb->ip_summed = CHECKSUM_UNNECESSARY; + skb_pull(pskb, TH_HEADER_LENGTH); + + if (likely(header->th_ch_flag == TH_HAS_PDU)) { + if (do_debug_data) + ctcm_pr_debug("ctcmpc: %s() came into th_has_pdu\n", + __FUNCTION__); + if ((fsm_getstate(grpptr->fsm) == MPCG_STATE_FLOWC) || + ((fsm_getstate(grpptr->fsm) == MPCG_STATE_READY) && + (header->th_seq_num != ch->th_seq_num + 1) && + (ch->th_seq_num != 0))) { + /* This is NOT the next segment * + * we are not the correct race winner * + * go away and let someone else win * + * BUT..this only applies if xid negot * + * is done * + */ + grpptr->out_of_sequence += 1; + __skb_push(pskb, TH_HEADER_LENGTH); + skb_queue_tail(&ch->io_queue, pskb); + if (do_debug_data) + ctcm_pr_debug("ctcmpc: %s() th_seq_num " + "expect:%08x got:%08x\n", __FUNCTION__, + ch->th_seq_num + 1, header->th_seq_num); + + return; + } + grpptr->out_of_sequence = 0; + ch->th_seq_num = header->th_seq_num; + + if (do_debug_data) + ctcm_pr_debug("ctcmpc: %s() FromVTAM_th_seq=%08x\n", + __FUNCTION__, ch->th_seq_num); + + if (unlikely(fsm_getstate(grpptr->fsm) != MPCG_STATE_READY)) + goto done; + pdu_last_seen = 0; + while ((pskb->len > 0) && !pdu_last_seen) { + curr_pdu = (struct pdu *)pskb->data; + if (do_debug_data) { + ctcm_pr_debug("ctcm: %s() pdu_header\n", + __FUNCTION__); + ctcmpc_dumpit((char *)pskb->data, + PDU_HEADER_LENGTH); + ctcm_pr_debug("ctcm: %s() pskb len: %04x \n", + __FUNCTION__, pskb->len); + } + skb_pull(pskb, PDU_HEADER_LENGTH); + + if (curr_pdu->pdu_flag & PDU_LAST) + pdu_last_seen = 1; + if (curr_pdu->pdu_flag & PDU_CNTL) + pskb->protocol = htons(ETH_P_SNAP); + else + pskb->protocol = htons(ETH_P_SNA_DIX); + + if ((pskb->len <= 0) || (pskb->len > ch->max_bufsize)) { + printk(KERN_INFO + "%s Illegal packet size %d " + "received " + "dropping\n", dev->name, + pskb->len); + privptr->stats.rx_dropped++; + privptr->stats.rx_length_errors++; + goto done; + } + skb_reset_mac_header(pskb); + new_len = curr_pdu->pdu_offset; + if (do_debug_data) + ctcm_pr_debug("ctcmpc: %s() new_len: %04x \n", + __FUNCTION__, new_len); + if ((new_len == 0) || (new_len > pskb->len)) { + /* should never happen */ + /* pskb len must be hosed...bail out */ + printk(KERN_INFO + "ctcmpc: %s(): invalid pdu" + " offset of %04x - data may be" + "lost\n", __FUNCTION__, new_len); + goto done; + } + skb = __dev_alloc_skb(new_len+4, GFP_ATOMIC); + + if (!skb) { + printk(KERN_INFO + "ctcm: %s Out of memory in " + "%s()- request-len:%04x \n", + dev->name, + __FUNCTION__, + new_len+4); + privptr->stats.rx_dropped++; + fsm_event(grpptr->fsm, + MPCG_EVENT_INOP, dev); + goto done; + } + + memcpy(skb_put(skb, new_len), + pskb->data, new_len); + + skb_reset_mac_header(skb); + skb->dev = pskb->dev; + skb->protocol = pskb->protocol; + skb->ip_summed = CHECKSUM_UNNECESSARY; + *((__u32 *) skb_push(skb, 4)) = ch->pdu_seq; + ch->pdu_seq++; + + if (do_debug_data) + ctcm_pr_debug("%s: ToDCM_pdu_seq= %08x\n", + __FUNCTION__, ch->pdu_seq); + + ctcm_pr_debug("ctcm: %s() skb:%0lx " + "skb len: %d \n", __FUNCTION__, + (unsigned long)skb, skb->len); + if (do_debug_data) { + ctcm_pr_debug("ctcmpc: %s() up to 32 bytes" + " of pdu_data sent\n", + __FUNCTION__); + ctcmpc_dump32((char *)skb->data, skb->len); + } + + skblen = skb->len; + sendrc = netif_rx(skb); + privptr->stats.rx_packets++; + privptr->stats.rx_bytes += skblen; + skb_pull(pskb, new_len); /* point to next PDU */ + } + } else { + mpcginfo = (struct mpcg_info *) + kmalloc(sizeof(struct mpcg_info), gfp_type()); + if (mpcginfo == NULL) + goto done; + + mpcginfo->ch = ch; + mpcginfo->th = header; + mpcginfo->skb = pskb; + ctcm_pr_debug("ctcmpc: %s() Not PDU - may be control pkt\n", + __FUNCTION__); + /* it's a sweep? */ + sweep = (struct th_sweep *)pskb->data; + mpcginfo->sweep = sweep; + if (header->th_ch_flag == TH_SWEEP_REQ) + mpc_rcvd_sweep_req(mpcginfo); + else if (header->th_ch_flag == TH_SWEEP_RESP) + mpc_rcvd_sweep_resp(mpcginfo); + else if (header->th_blk_flag == TH_DATA_IS_XID) { + struct xid2 *thisxid = (struct xid2 *)pskb->data; + skb_pull(pskb, XID2_LENGTH); + mpcginfo->xid = thisxid; + fsm_event(grpptr->fsm, MPCG_EVENT_XID2, mpcginfo); + } else if (header->th_blk_flag == TH_DISCONTACT) + fsm_event(grpptr->fsm, MPCG_EVENT_DISCONC, mpcginfo); + else if (header->th_seq_num != 0) { + printk(KERN_INFO "%s unexpected packet" + " expected control pkt\n", dev->name); + privptr->stats.rx_dropped++; + /* mpcginfo only used for non-data transfers */ + kfree(mpcginfo); + if (do_debug_data) + ctcmpc_dump_skb(pskb, -8); + } + } +done: + + dev_kfree_skb_any(pskb); + if (sendrc == NET_RX_DROP) { + printk(KERN_WARNING "%s %s() NETWORK BACKLOG EXCEEDED" + " - PACKET DROPPED\n", dev->name, __FUNCTION__); + fsm_event(grpptr->fsm, MPCG_EVENT_INOP, dev); + } + + if (do_debug) + ctcm_pr_debug("ctcmpc exit : %s %s(): ch=0x%p id=%s\n", + dev->name, __FUNCTION__, ch, ch->id); +} + +/** + * tasklet helper for mpc's skb unpacking. + * + * @param ch The channel to work on. + * Allow flow control back pressure to occur here. + * Throttling back channel can result in excessive + * channel inactivity and system deact of channel + */ +void ctcmpc_bh(unsigned long thischan) +{ + struct channel *ch = (struct channel *)thischan; + struct sk_buff *skb; + struct net_device *dev = ch->netdev; + struct ctcm_priv *privptr = (struct ctcm_priv *)dev->priv; + struct mpc_group *grpptr = privptr->mpcg; + + if (do_debug) + ctcm_pr_debug("%s cp:%i enter: %s() %s\n", + dev->name, smp_processor_id(), __FUNCTION__, ch->id); + /* caller has requested driver to throttle back */ + while ((fsm_getstate(grpptr->fsm) != MPCG_STATE_FLOWC) && + (skb = skb_dequeue(&ch->io_queue))) { + ctcmpc_unpack_skb(ch, skb); + if (grpptr->out_of_sequence > 20) { + /* assume data loss has occurred if */ + /* missing seq_num for extended */ + /* period of time */ + grpptr->out_of_sequence = 0; + fsm_event(grpptr->fsm, MPCG_EVENT_INOP, dev); + break; + } + if (skb == skb_peek(&ch->io_queue)) + break; + } + if (do_debug) + ctcm_pr_debug("ctcmpc exit : %s %s(): ch=0x%p id=%s\n", + dev->name, __FUNCTION__, ch, ch->id); + return; +} + +/* + * MPC Group Initializations + */ +struct mpc_group *ctcmpc_init_mpc_group(struct ctcm_priv *privptr) +{ + struct mpc_group *grpptr; + + CTCM_DBF_TEXT(MPC_SETUP, 3, __FUNCTION__); + + grpptr = kzalloc(sizeof(struct mpc_group), GFP_KERNEL); + if (grpptr == NULL) + return NULL; + + grpptr->fsm = + init_fsm("mpcg", mpcg_state_names, mpcg_event_names, + MPCG_NR_STATES, MPCG_NR_EVENTS, mpcg_fsm, + mpcg_fsm_len, GFP_KERNEL); + if (grpptr->fsm == NULL) { + kfree(grpptr); + return NULL; + } + + fsm_newstate(grpptr->fsm, MPCG_STATE_RESET); + fsm_settimer(grpptr->fsm, &grpptr->timer); + + grpptr->xid_skb = + __dev_alloc_skb(MPC_BUFSIZE_DEFAULT, GFP_ATOMIC | GFP_DMA); + if (grpptr->xid_skb == NULL) { + printk(KERN_INFO "Couldn't alloc MPCgroup xid_skb\n"); + kfree_fsm(grpptr->fsm); + kfree(grpptr); + return NULL; + } + /* base xid for all channels in group */ + grpptr->xid_skb_data = grpptr->xid_skb->data; + grpptr->xid_th = (struct th_header *)grpptr->xid_skb->data; + memcpy(skb_put(grpptr->xid_skb, TH_HEADER_LENGTH), + &thnorm, TH_HEADER_LENGTH); + + grpptr->xid = (struct xid2 *) skb_tail_pointer(grpptr->xid_skb); + memcpy(skb_put(grpptr->xid_skb, XID2_LENGTH), &init_xid, XID2_LENGTH); + grpptr->xid->xid2_adj_id = jiffies | 0xfff00000; + grpptr->xid->xid2_sender_id = jiffies; + + grpptr->xid_id = skb_tail_pointer(grpptr->xid_skb); + memcpy(skb_put(grpptr->xid_skb, 4), "VTAM", 4); + + grpptr->rcvd_xid_skb = + __dev_alloc_skb(MPC_BUFSIZE_DEFAULT, GFP_ATOMIC|GFP_DMA); + if (grpptr->rcvd_xid_skb == NULL) { + printk(KERN_INFO "Couldn't alloc MPCgroup rcvd_xid_skb\n"); + kfree_fsm(grpptr->fsm); + dev_kfree_skb(grpptr->xid_skb); + kfree(grpptr); + return NULL; + } + grpptr->rcvd_xid_data = grpptr->rcvd_xid_skb->data; + grpptr->rcvd_xid_th = (struct th_header *)grpptr->rcvd_xid_skb->data; + memcpy(skb_put(grpptr->rcvd_xid_skb, TH_HEADER_LENGTH), + &thnorm, TH_HEADER_LENGTH); + grpptr->saved_xid2 = NULL; + privptr->xid = grpptr->xid; + privptr->mpcg = grpptr; + return grpptr; +} + +/* + * The MPC Group Station FSM + */ + +/* + * MPC Group Station FSM actions + * CTCM_PROTO_MPC only + */ + +/* + * invoked when the device transitions to dev_stopped + * MPC will stop each individual channel if a single XID failure + * occurs, or will intitiate all channels be stopped if a GROUP + * level failure occurs. + */ +static void mpc_action_go_inop(fsm_instance *fi, int event, void *arg) +{ + struct net_device *dev = (struct net_device *)arg; + struct ctcm_priv *privptr; + struct mpc_group *grpptr; + int rc = 0; + struct channel *wch, *rch; + + if (dev == NULL) { + printk(KERN_INFO "%s() dev=NULL\n", __FUNCTION__); + return; + } + + ctcm_pr_debug("ctcmpc enter: %s %s()\n", dev->name, __FUNCTION__); + + privptr = (struct ctcm_priv *)dev->priv; + grpptr = privptr->mpcg; + grpptr->flow_off_called = 0; + + fsm_deltimer(&grpptr->timer); + + if (grpptr->channels_terminating) + goto done; + + grpptr->channels_terminating = 1; + + grpptr->saved_state = fsm_getstate(grpptr->fsm); + fsm_newstate(grpptr->fsm, MPCG_STATE_INOP); + if (grpptr->saved_state > MPCG_STATE_XID7INITF) + printk(KERN_NOTICE "%s:MPC GROUP INOPERATIVE\n", dev->name); + if ((grpptr->saved_state != MPCG_STATE_RESET) || + /* dealloc_channel has been called */ + ((grpptr->saved_state == MPCG_STATE_RESET) && + (grpptr->port_persist == 0))) + fsm_deltimer(&privptr->restart_timer); + + wch = privptr->channel[WRITE]; + rch = privptr->channel[READ]; + + switch (grpptr->saved_state) { + case MPCG_STATE_RESET: + case MPCG_STATE_INOP: + case MPCG_STATE_XID2INITW: + case MPCG_STATE_XID0IOWAIT: + case MPCG_STATE_XID2INITX: + case MPCG_STATE_XID7INITW: + case MPCG_STATE_XID7INITX: + case MPCG_STATE_XID0IOWAIX: + case MPCG_STATE_XID7INITI: + case MPCG_STATE_XID7INITZ: + case MPCG_STATE_XID7INITF: + break; + case MPCG_STATE_FLOWC: + case MPCG_STATE_READY: + default: + tasklet_hi_schedule(&wch->ch_disc_tasklet); + } + + grpptr->xid2_tgnum = 0; + grpptr->group_max_buflen = 0; /*min of all received */ + grpptr->outstanding_xid2 = 0; + grpptr->outstanding_xid7 = 0; + grpptr->outstanding_xid7_p2 = 0; + grpptr->saved_xid2 = NULL; + grpptr->xidnogood = 0; + grpptr->changed_side = 0; + + grpptr->rcvd_xid_skb->data = grpptr->rcvd_xid_data; + skb_reset_tail_pointer(grpptr->rcvd_xid_skb); + grpptr->rcvd_xid_skb->len = 0; + grpptr->rcvd_xid_th = (struct th_header *)grpptr->rcvd_xid_skb->data; + memcpy(skb_put(grpptr->rcvd_xid_skb, TH_HEADER_LENGTH), &thnorm, + TH_HEADER_LENGTH); + + if (grpptr->send_qllc_disc == 1) { + grpptr->send_qllc_disc = 0; + rc = mpc_send_qllc_discontact(dev); + } + + /* DO NOT issue DEV_EVENT_STOP directly out of this code */ + /* This can result in INOP of VTAM PU due to halting of */ + /* outstanding IO which causes a sense to be returned */ + /* Only about 3 senses are allowed and then IOS/VTAM will*/ + /* ebcome unreachable without manual intervention */ + if ((grpptr->port_persist == 1) || (grpptr->alloc_called)) { + grpptr->alloc_called = 0; + fsm_deltimer(&privptr->restart_timer); + fsm_addtimer(&privptr->restart_timer, + 500, + DEV_EVENT_RESTART, + dev); + fsm_newstate(grpptr->fsm, MPCG_STATE_RESET); + if (grpptr->saved_state > MPCG_STATE_XID7INITF) + printk(KERN_NOTICE "%s:MPC GROUP RECOVERY SCHEDULED\n", + dev->name); + } else { + fsm_deltimer(&privptr->restart_timer); + fsm_addtimer(&privptr->restart_timer, 500, DEV_EVENT_STOP, dev); + fsm_newstate(grpptr->fsm, MPCG_STATE_RESET); + printk(KERN_NOTICE "%s:MPC GROUP RECOVERY NOT ATTEMPTED\n", + dev->name); + } + +done: + ctcm_pr_debug("ctcmpc exit:%s %s()\n", dev->name, __FUNCTION__); + return; +} + +/** + * Handle mpc group action timeout. + * MPC Group Station FSM action + * CTCM_PROTO_MPC only + * + * @param fi An instance of an mpc_group fsm. + * @param event The event, just happened. + * @param arg Generic pointer, casted from net_device * upon call. + */ +static void mpc_action_timeout(fsm_instance *fi, int event, void *arg) +{ + struct net_device *dev = (struct net_device *)arg; + struct ctcm_priv *privptr; + struct mpc_group *grpptr; + struct channel *wch; + struct channel *rch; + + CTCM_DBF_TEXT(MPC_TRACE, 6, __FUNCTION__); + + if (dev == NULL) { + CTCM_DBF_TEXT_(MPC_ERROR, 4, "%s: dev=NULL\n", __FUNCTION__); + return; + } + + privptr = (struct ctcm_priv *)dev->priv; + grpptr = privptr->mpcg; + wch = privptr->channel[WRITE]; + rch = privptr->channel[READ]; + + switch (fsm_getstate(grpptr->fsm)) { + case MPCG_STATE_XID2INITW: + /* Unless there is outstanding IO on the */ + /* channel just return and wait for ATTN */ + /* interrupt to begin XID negotiations */ + if ((fsm_getstate(rch->fsm) == CH_XID0_PENDING) && + (fsm_getstate(wch->fsm) == CH_XID0_PENDING)) + break; + default: + fsm_event(grpptr->fsm, MPCG_EVENT_INOP, dev); + } + + CTCM_DBF_TEXT_(MPC_TRACE, 6, "%s: dev=%s exit", + __FUNCTION__, dev->name); + return; +} + +/* + * MPC Group Station FSM action + * CTCM_PROTO_MPC only + */ +void mpc_action_discontact(fsm_instance *fi, int event, void *arg) +{ + struct mpcg_info *mpcginfo = (struct mpcg_info *)arg; + struct channel *ch = mpcginfo->ch; + struct net_device *dev = ch->netdev; + struct ctcm_priv *privptr = (struct ctcm_priv *)dev->priv; + struct mpc_group *grpptr = privptr->mpcg; + + if (ch == NULL) { + printk(KERN_INFO "%s() ch=NULL\n", __FUNCTION__); + return; + } + if (ch->netdev == NULL) { + printk(KERN_INFO "%s() dev=NULL\n", __FUNCTION__); + return; + } + + ctcm_pr_debug("ctcmpc enter: %s %s()\n", dev->name, __FUNCTION__); + + grpptr->send_qllc_disc = 1; + fsm_event(grpptr->fsm, MPCG_EVENT_INOP, dev); + + ctcm_pr_debug("ctcmpc exit: %s %s()\n", dev->name, __FUNCTION__); + return; +} + +/* + * MPC Group Station - not part of FSM + * CTCM_PROTO_MPC only + * called from add_channel in ctcm_main.c + */ +void mpc_action_send_discontact(unsigned long thischan) +{ + struct channel *ch; + struct net_device *dev; + struct ctcm_priv *privptr; + struct mpc_group *grpptr; + int rc = 0; + unsigned long saveflags; + + ch = (struct channel *)thischan; + dev = ch->netdev; + privptr = (struct ctcm_priv *)dev->priv; + grpptr = privptr->mpcg; + + ctcm_pr_info("ctcmpc: %s cp:%i enter: %s() GrpState:%s ChState:%s\n", + dev->name, + smp_processor_id(), + __FUNCTION__, + fsm_getstate_str(grpptr->fsm), + fsm_getstate_str(ch->fsm)); + saveflags = 0; /* avoids compiler warning with + spin_unlock_irqrestore */ + + spin_lock_irqsave(get_ccwdev_lock(ch->cdev), saveflags); + rc = ccw_device_start(ch->cdev, &ch->ccw[15], + (unsigned long)ch, 0xff, 0); + spin_unlock_irqrestore(get_ccwdev_lock(ch->cdev), saveflags); + + if (rc != 0) { + ctcm_pr_info("ctcmpc: %s() ch:%s IO failed \n", + __FUNCTION__, + ch->id); + ctcm_ccw_check_rc(ch, rc, "send discontact"); + /* Not checking return code value here */ + /* Making best effort to notify partner*/ + /* that MPC Group is going down */ + } + + ctcm_pr_debug("ctcmpc exit: %s %s()\n", dev->name, __FUNCTION__); + return; +} + + +/* + * helper function of mpc FSM + * CTCM_PROTO_MPC only + * mpc_action_rcvd_xid7 +*/ +static int mpc_validate_xid(struct mpcg_info *mpcginfo) +{ + struct channel *ch = mpcginfo->ch; + struct net_device *dev = ch->netdev; + struct ctcm_priv *privptr = (struct ctcm_priv *)dev->priv; + struct mpc_group *grpptr = privptr->mpcg; + struct xid2 *xid = mpcginfo->xid; + int failed = 0; + int rc = 0; + __u64 our_id, their_id = 0; + int len; + + len = TH_HEADER_LENGTH + PDU_HEADER_LENGTH; + + ctcm_pr_debug("ctcmpc enter: %s()\n", __FUNCTION__); + + if (mpcginfo->xid == NULL) { + printk(KERN_INFO "%s() xid=NULL\n", __FUNCTION__); + rc = 1; + goto done; + } + + ctcm_pr_debug("ctcmpc : %s xid received()\n", __FUNCTION__); + ctcmpc_dumpit((char *)mpcginfo->xid, XID2_LENGTH); + + /*the received direction should be the opposite of ours */ + if (((CHANNEL_DIRECTION(ch->flags) == READ) ? XID2_WRITE_SIDE : + XID2_READ_SIDE) != xid->xid2_dlc_type) { + failed = 1; + printk(KERN_INFO "ctcmpc:%s() XID REJECTED - READ-WRITE CH " + "Pairing Invalid \n", __FUNCTION__); + } + + if (xid->xid2_dlc_type == XID2_READ_SIDE) { + ctcm_pr_debug("ctcmpc: %s(): grpmaxbuf:%d xid2buflen:%d\n", + __FUNCTION__, grpptr->group_max_buflen, + xid->xid2_buf_len); + + if (grpptr->group_max_buflen == 0 || + grpptr->group_max_buflen > xid->xid2_buf_len - len) + grpptr->group_max_buflen = xid->xid2_buf_len - len; + } + + + if (grpptr->saved_xid2 == NULL) { + grpptr->saved_xid2 = + (struct xid2 *)skb_tail_pointer(grpptr->rcvd_xid_skb); + + memcpy(skb_put(grpptr->rcvd_xid_skb, + XID2_LENGTH), xid, XID2_LENGTH); + grpptr->rcvd_xid_skb->data = grpptr->rcvd_xid_data; + + skb_reset_tail_pointer(grpptr->rcvd_xid_skb); + grpptr->rcvd_xid_skb->len = 0; + + /* convert two 32 bit numbers into 1 64 bit for id compare */ + our_id = (__u64)privptr->xid->xid2_adj_id; + our_id = our_id << 32; + our_id = our_id + privptr->xid->xid2_sender_id; + their_id = (__u64)xid->xid2_adj_id; + their_id = their_id << 32; + their_id = their_id + xid->xid2_sender_id; + /* lower id assume the xside role */ + if (our_id < their_id) { + grpptr->roll = XSIDE; + ctcm_pr_debug("ctcmpc :%s() WE HAVE LOW ID-" + "TAKE XSIDE\n", __FUNCTION__); + } else { + grpptr->roll = YSIDE; + ctcm_pr_debug("ctcmpc :%s() WE HAVE HIGH ID-" + "TAKE YSIDE\n", __FUNCTION__); + } + + } else { + if (xid->xid2_flag4 != grpptr->saved_xid2->xid2_flag4) { + failed = 1; + printk(KERN_INFO "%s XID REJECTED - XID Flag Byte4\n", + __FUNCTION__); + } + if (xid->xid2_flag2 == 0x40) { + failed = 1; + printk(KERN_INFO "%s XID REJECTED - XID NOGOOD\n", + __FUNCTION__); + } + if (xid->xid2_adj_id != grpptr->saved_xid2->xid2_adj_id) { + failed = 1; + printk(KERN_INFO "%s XID REJECTED - " + "Adjacent Station ID Mismatch\n", + __FUNCTION__); + } + if (xid->xid2_sender_id != grpptr->saved_xid2->xid2_sender_id) { + failed = 1; + printk(KERN_INFO "%s XID REJECTED - " + "Sender Address Mismatch\n", __FUNCTION__); + + } + } + + if (failed) { + ctcm_pr_info("ctcmpc : %s() failed\n", __FUNCTION__); + privptr->xid->xid2_flag2 = 0x40; + grpptr->saved_xid2->xid2_flag2 = 0x40; + rc = 1; + } + +done: + + ctcm_pr_debug("ctcmpc exit: %s()\n", __FUNCTION__); + return rc; +} + +/* + * MPC Group Station FSM action + * CTCM_PROTO_MPC only + */ +static void mpc_action_side_xid(fsm_instance *fsm, void *arg, int side) +{ + struct channel *ch = (struct channel *)arg; + struct ctcm_priv *privptr; + struct mpc_group *grpptr = NULL; + struct net_device *dev = NULL; + int rc = 0; + int gotlock = 0; + unsigned long saveflags = 0; /* avoids compiler warning with + spin_unlock_irqrestore */ + + if (ch == NULL) { + printk(KERN_INFO "%s ch=NULL\n", __FUNCTION__); + goto done; + } + + if (do_debug) + ctcm_pr_debug("ctcmpc enter: %s(): cp=%i ch=0x%p id=%s\n", + __FUNCTION__, smp_processor_id(), ch, ch->id); + + dev = ch->netdev; + if (dev == NULL) { + printk(KERN_INFO "%s dev=NULL\n", __FUNCTION__); + goto done; + } + + privptr = (struct ctcm_priv *)dev->priv; + if (privptr == NULL) { + printk(KERN_INFO "%s privptr=NULL\n", __FUNCTION__); + goto done; + } + + grpptr = privptr->mpcg; + if (grpptr == NULL) { + printk(KERN_INFO "%s grpptr=NULL\n", __FUNCTION__); + goto done; + } + + if (ctcm_checkalloc_buffer(ch)) + goto done; + + /* skb data-buffer referencing: */ + + ch->trans_skb->data = ch->trans_skb_data; + skb_reset_tail_pointer(ch->trans_skb); + ch->trans_skb->len = 0; + /* result of the previous 3 statements is NOT always + * already set after ctcm_checkalloc_buffer + * because of possible reuse of the trans_skb + */ + memset(ch->trans_skb->data, 0, 16); + ch->rcvd_xid_th = (struct th_header *)ch->trans_skb_data; + /* check is main purpose here: */ + skb_put(ch->trans_skb, TH_HEADER_LENGTH); + ch->rcvd_xid = (struct xid2 *)skb_tail_pointer(ch->trans_skb); + /* check is main purpose here: */ + skb_put(ch->trans_skb, XID2_LENGTH); + ch->rcvd_xid_id = skb_tail_pointer(ch->trans_skb); + /* cleanup back to startpoint */ + ch->trans_skb->data = ch->trans_skb_data; + skb_reset_tail_pointer(ch->trans_skb); + ch->trans_skb->len = 0; + + /* non-checking rewrite of above skb data-buffer referencing: */ + /* + memset(ch->trans_skb->data, 0, 16); + ch->rcvd_xid_th = (struct th_header *)ch->trans_skb_data; + ch->rcvd_xid = (struct xid2 *)(ch->trans_skb_data + TH_HEADER_LENGTH); + ch->rcvd_xid_id = ch->trans_skb_data + TH_HEADER_LENGTH + XID2_LENGTH; + */ + + ch->ccw[8].flags = CCW_FLAG_SLI | CCW_FLAG_CC; + ch->ccw[8].count = 0; + ch->ccw[8].cda = 0x00; + + if (side == XSIDE) { + /* mpc_action_xside_xid */ + if (ch->xid_th == NULL) { + printk(KERN_INFO "%s ch->xid_th=NULL\n", __FUNCTION__); + goto done; + } + ch->ccw[9].cmd_code = CCW_CMD_WRITE; + ch->ccw[9].flags = CCW_FLAG_SLI | CCW_FLAG_CC; + ch->ccw[9].count = TH_HEADER_LENGTH; + ch->ccw[9].cda = virt_to_phys(ch->xid_th); + + if (ch->xid == NULL) { + printk(KERN_INFO "%s ch->xid=NULL\n", __FUNCTION__); + goto done; + } + + ch->ccw[10].cmd_code = CCW_CMD_WRITE; + ch->ccw[10].flags = CCW_FLAG_SLI | CCW_FLAG_CC; + ch->ccw[10].count = XID2_LENGTH; + ch->ccw[10].cda = virt_to_phys(ch->xid); + + ch->ccw[11].cmd_code = CCW_CMD_READ; + ch->ccw[11].flags = CCW_FLAG_SLI | CCW_FLAG_CC; + ch->ccw[11].count = TH_HEADER_LENGTH; + ch->ccw[11].cda = virt_to_phys(ch->rcvd_xid_th); + + ch->ccw[12].cmd_code = CCW_CMD_READ; + ch->ccw[12].flags = CCW_FLAG_SLI | CCW_FLAG_CC; + ch->ccw[12].count = XID2_LENGTH; + ch->ccw[12].cda = virt_to_phys(ch->rcvd_xid); + + ch->ccw[13].cmd_code = CCW_CMD_READ; + ch->ccw[13].cda = virt_to_phys(ch->rcvd_xid_id); + + } else { /* side == YSIDE : mpc_action_yside_xid */ + ch->ccw[9].cmd_code = CCW_CMD_READ; + ch->ccw[9].flags = CCW_FLAG_SLI | CCW_FLAG_CC; + ch->ccw[9].count = TH_HEADER_LENGTH; + ch->ccw[9].cda = virt_to_phys(ch->rcvd_xid_th); + + ch->ccw[10].cmd_code = CCW_CMD_READ; + ch->ccw[10].flags = CCW_FLAG_SLI | CCW_FLAG_CC; + ch->ccw[10].count = XID2_LENGTH; + ch->ccw[10].cda = virt_to_phys(ch->rcvd_xid); + + if (ch->xid_th == NULL) { + printk(KERN_INFO "%s ch->xid_th=NULL\n", __FUNCTION__); + goto done; + } + ch->ccw[11].cmd_code = CCW_CMD_WRITE; + ch->ccw[11].flags = CCW_FLAG_SLI | CCW_FLAG_CC; + ch->ccw[11].count = TH_HEADER_LENGTH; + ch->ccw[11].cda = virt_to_phys(ch->xid_th); + + if (ch->xid == NULL) { + printk(KERN_INFO "%s ch->xid=NULL\n", __FUNCTION__); + goto done; + } + ch->ccw[12].cmd_code = CCW_CMD_WRITE; + ch->ccw[12].flags = CCW_FLAG_SLI | CCW_FLAG_CC; + ch->ccw[12].count = XID2_LENGTH; + ch->ccw[12].cda = virt_to_phys(ch->xid); + + if (ch->xid_id == NULL) { + printk(KERN_INFO "%s ch->xid_id=NULL\n", __FUNCTION__); + goto done; + } + ch->ccw[13].cmd_code = CCW_CMD_WRITE; + ch->ccw[13].cda = virt_to_phys(ch->xid_id); + + } + ch->ccw[13].flags = CCW_FLAG_SLI | CCW_FLAG_CC; + ch->ccw[13].count = 4; + + ch->ccw[14].cmd_code = CCW_CMD_NOOP; + ch->ccw[14].flags = CCW_FLAG_SLI; + ch->ccw[14].count = 0; + ch->ccw[14].cda = 0; + + if (do_debug_ccw) + ctcmpc_dumpit((char *)&ch->ccw[8], sizeof(struct ccw1) * 7); + + ctcmpc_dumpit((char *)ch->xid_th, TH_HEADER_LENGTH); + ctcmpc_dumpit((char *)ch->xid, XID2_LENGTH); + ctcmpc_dumpit((char *)ch->xid_id, 4); + if (!in_irq()) { + /* Such conditional locking is a known problem for + * sparse because its static undeterministic. + * Warnings should be ignored here. */ + spin_lock_irqsave(get_ccwdev_lock(ch->cdev), saveflags); + gotlock = 1; + } + + fsm_addtimer(&ch->timer, 5000 , CTC_EVENT_TIMER, ch); + rc = ccw_device_start(ch->cdev, &ch->ccw[8], + (unsigned long)ch, 0xff, 0); + + if (gotlock) /* see remark above about conditional locking */ + spin_unlock_irqrestore(get_ccwdev_lock(ch->cdev), saveflags); + + if (rc != 0) { + ctcm_pr_info("ctcmpc: %s() ch:%s IO failed \n", + __FUNCTION__, ch->id); + ctcm_ccw_check_rc(ch, rc, + (side == XSIDE) ? "x-side XID" : "y-side XID"); + } + +done: + if (do_debug) + ctcm_pr_debug("ctcmpc exit : %s(): ch=0x%p id=%s\n", + __FUNCTION__, ch, ch->id); + return; + +} + +/* + * MPC Group Station FSM action + * CTCM_PROTO_MPC only + */ +static void mpc_action_xside_xid(fsm_instance *fsm, int event, void *arg) +{ + mpc_action_side_xid(fsm, arg, XSIDE); +} + +/* + * MPC Group Station FSM action + * CTCM_PROTO_MPC only + */ +static void mpc_action_yside_xid(fsm_instance *fsm, int event, void *arg) +{ + mpc_action_side_xid(fsm, arg, YSIDE); +} + +/* + * MPC Group Station FSM action + * CTCM_PROTO_MPC only + */ +static void mpc_action_doxid0(fsm_instance *fsm, int event, void *arg) +{ + struct channel *ch; + struct ctcm_priv *privptr; + struct mpc_group *grpptr = NULL; + struct net_device *dev = NULL; + + ch = (struct channel *)arg; + + if (do_debug) + ctcm_pr_debug("ctcmpc enter: %s(): cp=%i ch=0x%p id=%s\n", + __FUNCTION__, smp_processor_id(), ch, ch->id); + + if (ch == NULL) { + printk(KERN_WARNING "%s ch=NULL\n", __FUNCTION__); + goto done; + } + + dev = ch->netdev; + if (dev == NULL) { + printk(KERN_WARNING "%s dev=NULL\n", __FUNCTION__); + goto done; + } + + privptr = (struct ctcm_priv *)dev->priv; + if (privptr == NULL) { + printk(KERN_WARNING "%s privptr=NULL\n", __FUNCTION__); + goto done; + } + + grpptr = privptr->mpcg; + if (grpptr == NULL) { + printk(KERN_WARNING "%s grpptr=NULL\n", __FUNCTION__); + goto done; + } + + if (ch->xid == NULL) { + printk(KERN_WARNING "%s ch-xid=NULL\n", __FUNCTION__); + goto done; + } + + fsm_newstate(ch->fsm, CH_XID0_INPROGRESS); + + ch->xid->xid2_option = XID2_0; + + switch (fsm_getstate(grpptr->fsm)) { + case MPCG_STATE_XID2INITW: + case MPCG_STATE_XID2INITX: + ch->ccw[8].cmd_code = CCW_CMD_SENSE_CMD; + break; + case MPCG_STATE_XID0IOWAIT: + case MPCG_STATE_XID0IOWAIX: + ch->ccw[8].cmd_code = CCW_CMD_WRITE_CTL; + break; + } + + fsm_event(grpptr->fsm, MPCG_EVENT_DOIO, ch); + +done: + if (do_debug) + ctcm_pr_debug("ctcmpc exit : %s(): ch=0x%p id=%s\n", + __FUNCTION__, ch, ch->id); + return; + +} + +/* + * MPC Group Station FSM action + * CTCM_PROTO_MPC only +*/ +static void mpc_action_doxid7(fsm_instance *fsm, int event, void *arg) +{ + struct net_device *dev = (struct net_device *)arg; + struct ctcm_priv *privptr = NULL; + struct mpc_group *grpptr = NULL; + int direction; + int rc = 0; + int send = 0; + + ctcm_pr_debug("ctcmpc enter: %s() \n", __FUNCTION__); + + if (dev == NULL) { + printk(KERN_INFO "%s dev=NULL \n", __FUNCTION__); + rc = 1; + goto done; + } + + privptr = (struct ctcm_priv *)dev->priv; + if (privptr == NULL) { + printk(KERN_INFO "%s privptr=NULL \n", __FUNCTION__); + rc = 1; + goto done; + } + + grpptr = privptr->mpcg; + if (grpptr == NULL) { + printk(KERN_INFO "%s grpptr=NULL \n", __FUNCTION__); + rc = 1; + goto done; + } + + for (direction = READ; direction <= WRITE; direction++) { + struct channel *ch = privptr->channel[direction]; + struct xid2 *thisxid = ch->xid; + ch->xid_skb->data = ch->xid_skb_data; + skb_reset_tail_pointer(ch->xid_skb); + ch->xid_skb->len = 0; + thisxid->xid2_option = XID2_7; + send = 0; + + /* xid7 phase 1 */ + if (grpptr->outstanding_xid7_p2 > 0) { + if (grpptr->roll == YSIDE) { + if (fsm_getstate(ch->fsm) == CH_XID7_PENDING1) { + fsm_newstate(ch->fsm, CH_XID7_PENDING2); + ch->ccw[8].cmd_code = CCW_CMD_SENSE_CMD; + memcpy(skb_put(ch->xid_skb, + TH_HEADER_LENGTH), + &thdummy, TH_HEADER_LENGTH); + send = 1; + } + } else if (fsm_getstate(ch->fsm) < CH_XID7_PENDING2) { + fsm_newstate(ch->fsm, CH_XID7_PENDING2); + ch->ccw[8].cmd_code = CCW_CMD_WRITE_CTL; + memcpy(skb_put(ch->xid_skb, + TH_HEADER_LENGTH), + &thnorm, TH_HEADER_LENGTH); + send = 1; + } + } else { + /* xid7 phase 2 */ + if (grpptr->roll == YSIDE) { + if (fsm_getstate(ch->fsm) < CH_XID7_PENDING4) { + fsm_newstate(ch->fsm, CH_XID7_PENDING4); + memcpy(skb_put(ch->xid_skb, + TH_HEADER_LENGTH), + &thnorm, TH_HEADER_LENGTH); + ch->ccw[8].cmd_code = CCW_CMD_WRITE_CTL; + send = 1; + } + } else if (fsm_getstate(ch->fsm) == CH_XID7_PENDING3) { + fsm_newstate(ch->fsm, CH_XID7_PENDING4); + ch->ccw[8].cmd_code = CCW_CMD_SENSE_CMD; + memcpy(skb_put(ch->xid_skb, TH_HEADER_LENGTH), + &thdummy, TH_HEADER_LENGTH); + send = 1; + } + } + + if (send) + fsm_event(grpptr->fsm, MPCG_EVENT_DOIO, ch); + } + +done: + + if (rc != 0) + fsm_event(grpptr->fsm, MPCG_EVENT_INOP, dev); + + return; +} + +/* + * MPC Group Station FSM action + * CTCM_PROTO_MPC only + */ +static void mpc_action_rcvd_xid0(fsm_instance *fsm, int event, void *arg) +{ + + struct mpcg_info *mpcginfo = (struct mpcg_info *)arg; + struct channel *ch = mpcginfo->ch; + struct net_device *dev = ch->netdev; + struct ctcm_priv *privptr; + struct mpc_group *grpptr; + + if (do_debug) + ctcm_pr_debug("ctcmpc enter: %s(): cp=%i ch=0x%p id=%s\n", + __FUNCTION__, smp_processor_id(), ch, ch->id); + + privptr = (struct ctcm_priv *)dev->priv; + grpptr = privptr->mpcg; + + ctcm_pr_debug("ctcmpc in:%s() %s xid2:%i xid7:%i xidt_p2:%i \n", + __FUNCTION__, ch->id, + grpptr->outstanding_xid2, + grpptr->outstanding_xid7, + grpptr->outstanding_xid7_p2); + + if (fsm_getstate(ch->fsm) < CH_XID7_PENDING) + fsm_newstate(ch->fsm, CH_XID7_PENDING); + + grpptr->outstanding_xid2--; + grpptr->outstanding_xid7++; + grpptr->outstanding_xid7_p2++; + + /* must change state before validating xid to */ + /* properly handle interim interrupts received*/ + switch (fsm_getstate(grpptr->fsm)) { + case MPCG_STATE_XID2INITW: + fsm_newstate(grpptr->fsm, MPCG_STATE_XID2INITX); + mpc_validate_xid(mpcginfo); + break; + case MPCG_STATE_XID0IOWAIT: + fsm_newstate(grpptr->fsm, MPCG_STATE_XID0IOWAIX); + mpc_validate_xid(mpcginfo); + break; + case MPCG_STATE_XID2INITX: + if (grpptr->outstanding_xid2 == 0) { + fsm_newstate(grpptr->fsm, MPCG_STATE_XID7INITW); + mpc_validate_xid(mpcginfo); + fsm_event(grpptr->fsm, MPCG_EVENT_XID2DONE, dev); + } + break; + case MPCG_STATE_XID0IOWAIX: + if (grpptr->outstanding_xid2 == 0) { + fsm_newstate(grpptr->fsm, MPCG_STATE_XID7INITI); + mpc_validate_xid(mpcginfo); + fsm_event(grpptr->fsm, MPCG_EVENT_XID2DONE, dev); + } + break; + } + kfree(mpcginfo); + + if (do_debug) { + ctcm_pr_debug("ctcmpc:%s() %s xid2:%i xid7:%i xidt_p2:%i \n", + __FUNCTION__, ch->id, + grpptr->outstanding_xid2, + grpptr->outstanding_xid7, + grpptr->outstanding_xid7_p2); + ctcm_pr_debug("ctcmpc:%s() %s grpstate: %s chanstate: %s \n", + __FUNCTION__, ch->id, + fsm_getstate_str(grpptr->fsm), + fsm_getstate_str(ch->fsm)); + } + return; + +} + + +/* + * MPC Group Station FSM action + * CTCM_PROTO_MPC only + */ +static void mpc_action_rcvd_xid7(fsm_instance *fsm, int event, void *arg) +{ + struct mpcg_info *mpcginfo = (struct mpcg_info *)arg; + struct channel *ch = mpcginfo->ch; + struct net_device *dev = ch->netdev; + struct ctcm_priv *privptr = (struct ctcm_priv *)dev->priv; + struct mpc_group *grpptr = privptr->mpcg; + + if (do_debug) { + ctcm_pr_debug("ctcmpc enter: %s(): cp=%i ch=0x%p id=%s\n", + __FUNCTION__, smp_processor_id(), ch, ch->id); + + ctcm_pr_debug("ctcmpc: outstanding_xid7: %i, " + " outstanding_xid7_p2: %i\n", + grpptr->outstanding_xid7, + grpptr->outstanding_xid7_p2); + } + + grpptr->outstanding_xid7--; + ch->xid_skb->data = ch->xid_skb_data; + skb_reset_tail_pointer(ch->xid_skb); + ch->xid_skb->len = 0; + + switch (fsm_getstate(grpptr->fsm)) { + case MPCG_STATE_XID7INITI: + fsm_newstate(grpptr->fsm, MPCG_STATE_XID7INITZ); + mpc_validate_xid(mpcginfo); + break; + case MPCG_STATE_XID7INITW: + fsm_newstate(grpptr->fsm, MPCG_STATE_XID7INITX); + mpc_validate_xid(mpcginfo); + break; + case MPCG_STATE_XID7INITZ: + case MPCG_STATE_XID7INITX: + if (grpptr->outstanding_xid7 == 0) { + if (grpptr->outstanding_xid7_p2 > 0) { + grpptr->outstanding_xid7 = + grpptr->outstanding_xid7_p2; + grpptr->outstanding_xid7_p2 = 0; + } else + fsm_newstate(grpptr->fsm, MPCG_STATE_XID7INITF); + + mpc_validate_xid(mpcginfo); + fsm_event(grpptr->fsm, MPCG_EVENT_XID7DONE, dev); + break; + } + mpc_validate_xid(mpcginfo); + break; + } + + kfree(mpcginfo); + + if (do_debug) + ctcm_pr_debug("ctcmpc exit: %s(): cp=%i ch=0x%p id=%s\n", + __FUNCTION__, smp_processor_id(), ch, ch->id); + return; + +} + +/* + * mpc_action helper of an MPC Group Station FSM action + * CTCM_PROTO_MPC only + */ +static int mpc_send_qllc_discontact(struct net_device *dev) +{ + int rc = 0; + __u32 new_len = 0; + struct sk_buff *skb; + struct qllc *qllcptr; + struct ctcm_priv *privptr; + struct mpc_group *grpptr; + + ctcm_pr_debug("ctcmpc enter: %s()\n", __FUNCTION__); + + if (dev == NULL) { + printk(KERN_INFO "%s() dev=NULL\n", __FUNCTION__); + rc = 1; + goto done; + } + + privptr = (struct ctcm_priv *)dev->priv; + if (privptr == NULL) { + printk(KERN_INFO "%s() privptr=NULL\n", __FUNCTION__); + rc = 1; + goto done; + } + + grpptr = privptr->mpcg; + if (grpptr == NULL) { + printk(KERN_INFO "%s() grpptr=NULL\n", __FUNCTION__); + rc = 1; + goto done; + } + ctcm_pr_info("ctcmpc: %s() GROUP STATE: %s\n", __FUNCTION__, + mpcg_state_names[grpptr->saved_state]); + + switch (grpptr->saved_state) { + /* + * establish conn callback function is + * preferred method to report failure + */ + case MPCG_STATE_XID0IOWAIT: + case MPCG_STATE_XID0IOWAIX: + case MPCG_STATE_XID7INITI: + case MPCG_STATE_XID7INITZ: + case MPCG_STATE_XID2INITW: + case MPCG_STATE_XID2INITX: + case MPCG_STATE_XID7INITW: + case MPCG_STATE_XID7INITX: + if (grpptr->estconnfunc) { + grpptr->estconnfunc(grpptr->port_num, -1, 0); + grpptr->estconnfunc = NULL; + break; + } + case MPCG_STATE_FLOWC: + case MPCG_STATE_READY: + grpptr->send_qllc_disc = 2; + new_len = sizeof(struct qllc); + qllcptr = kzalloc(new_len, gfp_type() | GFP_DMA); + if (qllcptr == NULL) { + printk(KERN_INFO + "ctcmpc: Out of memory in %s()\n", + dev->name); + rc = 1; + goto done; + } + + qllcptr->qllc_address = 0xcc; + qllcptr->qllc_commands = 0x03; + + skb = __dev_alloc_skb(new_len, GFP_ATOMIC); + + if (skb == NULL) { + printk(KERN_INFO "%s Out of memory in mpc_send_qllc\n", + dev->name); + privptr->stats.rx_dropped++; + rc = 1; + kfree(qllcptr); + goto done; + } + + memcpy(skb_put(skb, new_len), qllcptr, new_len); + kfree(qllcptr); + + if (skb_headroom(skb) < 4) { + printk(KERN_INFO "ctcmpc: %s() Unable to" + " build discontact for %s\n", + __FUNCTION__, dev->name); + rc = 1; + dev_kfree_skb_any(skb); + goto done; + } + + *((__u32 *)skb_push(skb, 4)) = privptr->channel[READ]->pdu_seq; + privptr->channel[READ]->pdu_seq++; + if (do_debug_data) + ctcm_pr_debug("ctcmpc: %s ToDCM_pdu_seq= %08x\n", + __FUNCTION__, privptr->channel[READ]->pdu_seq); + + /* receipt of CC03 resets anticipated sequence number on + receiving side */ + privptr->channel[READ]->pdu_seq = 0x00; + skb_reset_mac_header(skb); + skb->dev = dev; + skb->protocol = htons(ETH_P_SNAP); + skb->ip_summed = CHECKSUM_UNNECESSARY; + + ctcmpc_dumpit((char *)skb->data, (sizeof(struct qllc) + 4)); + + netif_rx(skb); + break; + default: + break; + + } + +done: + ctcm_pr_debug("ctcmpc exit: %s()\n", __FUNCTION__); + return rc; +} +/* --- This is the END my friend --- */ + Index: linux-2.6-uschi/drivers/s390/net/ctcm_mpc.h =================================================================== --- /dev/null +++ linux-2.6-uschi/drivers/s390/net/ctcm_mpc.h @@ -0,0 +1,239 @@ +/* + * drivers/s390/net/ctcm_mpc.h + * + * Copyright IBM Corp. 2007 + * Authors: Peter Tiedemann (ptiedem@de.ibm.com) + * + * MPC additions: + * Belinda Thompson (belindat@us.ibm.com) + * Andy Richter (richtera@us.ibm.com) + */ + +#ifndef _CTC_MPC_H_ +#define _CTC_MPC_H_ + +#include <linux/skbuff.h> +#include "fsm.h" + +/* + * MPC external interface + * Note that ctc_mpc_xyz are called with a lock on ................ + */ + +/* port_number is the mpc device 0, 1, 2 etc mpc2 is port_number 2 */ + +/* passive open Just wait for XID2 exchange */ +extern int ctc_mpc_alloc_channel(int port, + void (*callback)(int port_num, int max_write_size)); +/* active open Alloc then send XID2 */ +extern void ctc_mpc_establish_connectivity(int port, + void (*callback)(int port_num, int rc, int max_write_size)); + +extern void ctc_mpc_dealloc_ch(int port); +extern void ctc_mpc_flow_control(int port, int flowc); + +/* + * other MPC Group prototypes and structures + */ + +#define ETH_P_SNA_DIX 0x80D5 + +/* + * Declaration of an XID2 + * + */ +#define ALLZEROS 0x0000000000000000 + +#define XID_FM2 0x20 +#define XID2_0 0x00 +#define XID2_7 0x07 +#define XID2_WRITE_SIDE 0x04 +#define XID2_READ_SIDE 0x05 + +struct xid2 { + __u8 xid2_type_id; + __u8 xid2_len; + __u32 xid2_adj_id; + __u8 xid2_rlen; + __u8 xid2_resv1; + __u8 xid2_flag1; + __u8 xid2_fmtt; + __u8 xid2_flag4; + __u16 xid2_resv2; + __u8 xid2_tgnum; + __u32 xid2_sender_id; + __u8 xid2_flag2; + __u8 xid2_option; + char xid2_resv3[8]; + __u16 xid2_resv4; + __u8 xid2_dlc_type; + __u16 xid2_resv5; + __u8 xid2_mpc_flag; + __u8 xid2_resv6; + __u16 xid2_buf_len; + char xid2_buffer[255 - (13 * sizeof(__u8) + + 2 * sizeof(__u32) + + 4 * sizeof(__u16) + + 8 * sizeof(char))]; +} __attribute__ ((packed)); + +#define XID2_LENGTH (sizeof(struct xid2)) + +struct th_header { + __u8 th_seg; + __u8 th_ch_flag; +#define TH_HAS_PDU 0xf0 +#define TH_IS_XID 0x01 +#define TH_SWEEP_REQ 0xfe +#define TH_SWEEP_RESP 0xff + __u8 th_blk_flag; +#define TH_DATA_IS_XID 0x80 +#define TH_RETRY 0x40 +#define TH_DISCONTACT 0xc0 +#define TH_SEG_BLK 0x20 +#define TH_LAST_SEG 0x10 +#define TH_PDU_PART 0x08 + __u8 th_is_xid; /* is 0x01 if this is XID */ + __u32 th_seq_num; +} __attribute__ ((packed)); + +struct th_addon { + __u32 th_last_seq; + __u32 th_resvd; +} __attribute__ ((packed)); + +struct th_sweep { + struct th_header th; + struct th_addon sw; +} __attribute__ ((packed)); + +#define TH_HEADER_LENGTH (sizeof(struct th_header)) +#define TH_SWEEP_LENGTH (sizeof(struct th_sweep)) + +#define PDU_LAST 0x80 +#define PDU_CNTL 0x40 +#define PDU_FIRST 0x20 + +struct pdu { + __u32 pdu_offset; + __u8 pdu_flag; + __u8 pdu_proto; /* 0x01 is APPN SNA */ + __u16 pdu_seq; +} __attribute__ ((packed)); + +#define PDU_HEADER_LENGTH (sizeof(struct pdu)) + +struct qllc { + __u8 qllc_address; +#define QLLC_REQ 0xFF +#define QLLC_RESP 0x00 + __u8 qllc_commands; +#define QLLC_DISCONNECT 0x53 +#define QLLC_UNSEQACK 0x73 +#define QLLC_SETMODE 0x93 +#define QLLC_EXCHID 0xBF +} __attribute__ ((packed)); + + +/* + * Definition of one MPC group + */ + +#define MAX_MPCGCHAN 10 +#define MPC_XID_TIMEOUT_VALUE 10000 +#define MPC_CHANNEL_ADD 0 +#define MPC_CHANNEL_REMOVE 1 +#define MPC_CHANNEL_ATTN 2 +#define XSIDE 1 +#define YSIDE 0 + +struct mpcg_info { + struct sk_buff *skb; + struct channel *ch; + struct xid2 *xid; + struct th_sweep *sweep; + struct th_header *th; +}; + +struct mpc_group { + struct tasklet_struct mpc_tasklet; + struct tasklet_struct mpc_tasklet2; + int changed_side; + int saved_state; + int channels_terminating; + int out_of_sequence; + int flow_off_called; + int port_num; + int port_persist; + int alloc_called; + __u32 xid2_adj_id; + __u8 xid2_tgnum; + __u32 xid2_sender_id; + int num_channel_paths; + int active_channels[2]; + __u16 group_max_buflen; + int outstanding_xid2; + int outstanding_xid7; + int outstanding_xid7_p2; + int sweep_req_pend_num; + int sweep_rsp_pend_num; + struct sk_buff *xid_skb; + char *xid_skb_data; + struct th_header *xid_th; + struct xid2 *xid; + char *xid_id; + struct th_header *rcvd_xid_th; + struct sk_buff *rcvd_xid_skb; + char *rcvd_xid_data; + __u8 in_sweep; + __u8 roll; + struct xid2 *saved_xid2; + void (*allochanfunc)(int, int); + int allocchan_callback_retries; + void (*estconnfunc)(int, int, int); + int estconn_callback_retries; + int estconn_called; + int xidnogood; + int send_qllc_disc; + fsm_timer timer; + fsm_instance *fsm; /* group xid fsm */ +}; + +#ifdef DEBUGDATA +void ctcmpc_dumpit(char *buf, int len); +#else +static inline void ctcmpc_dumpit(char *buf, int len) +{ +} +#endif + +#ifdef DEBUGDATA +/* + * Dump header and first 16 bytes of an sk_buff for debugging purposes. + * + * @param skb The struct sk_buff to dump. + * @param offset Offset relative to skb-data, where to start the dump. + */ +void ctcmpc_dump_skb(struct sk_buff *skb, int offset); +#else +static inline void ctcmpc_dump_skb(struct sk_buff *skb, int offset) +{} +#endif + +static inline void ctcmpc_dump32(char *buf, int len) +{ + if (len < 32) + ctcmpc_dumpit(buf, len); + else + ctcmpc_dumpit(buf, 32); +} + +int ctcmpc_open(struct net_device *); +void ctcm_ccw_check_rc(struct channel *, int, char *); +void mpc_group_ready(unsigned long adev); +int mpc_channel_action(struct channel *ch, int direction, int action); +void mpc_action_send_discontact(unsigned long thischan); +void mpc_action_discontact(fsm_instance *fi, int event, void *arg); +void ctcmpc_bh(unsigned long thischan); +#endif +/* --- This is the END my friend --- */ Index: linux-2.6-uschi/drivers/s390/net/ctcm_sysfs.c =================================================================== --- /dev/null +++ linux-2.6-uschi/drivers/s390/net/ctcm_sysfs.c @@ -0,0 +1,210 @@ +/* + * drivers/s390/net/ctcm_sysfs.c + * + * Copyright IBM Corp. 2007, 2007 + * Authors: Peter Tiedemann (ptiedem@de.ibm.com) + * + */ + +#undef DEBUG +#undef DEBUGDATA +#undef DEBUGCCW + +#include <linux/sysfs.h> +#include "ctcm_main.h" + +/* + * sysfs attributes + */ + +static ssize_t ctcm_buffer_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct ctcm_priv *priv = dev_get_drvdata(dev); + + if (!priv) + return -ENODEV; + return sprintf(buf, "%d\n", priv->buffer_size); +} + +static ssize_t ctcm_buffer_write(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct net_device *ndev; + int bs1; + struct ctcm_priv *priv = dev_get_drvdata(dev); + + if (!(priv && priv->channel[READ] && + (ndev = priv->channel[READ]->netdev))) { + CTCM_DBF_TEXT(SETUP, CTC_DBF_ERROR, "bfnondev"); + return -ENODEV; + } + + sscanf(buf, "%u", &bs1); + if (bs1 > CTCM_BUFSIZE_LIMIT) + goto einval; + if (bs1 < (576 + LL_HEADER_LENGTH + 2)) + goto einval; + priv->buffer_size = bs1; /* just to overwrite the default */ + + if ((ndev->flags & IFF_RUNNING) && + (bs1 < (ndev->mtu + LL_HEADER_LENGTH + 2))) + goto einval; + + priv->channel[READ]->max_bufsize = bs1; + priv->channel[WRITE]->max_bufsize = bs1; + if (!(ndev->flags & IFF_RUNNING)) + ndev->mtu = bs1 - LL_HEADER_LENGTH - 2; + priv->channel[READ]->flags |= CHANNEL_FLAGS_BUFSIZE_CHANGED; + priv->channel[WRITE]->flags |= CHANNEL_FLAGS_BUFSIZE_CHANGED; + + CTCM_DBF_DEV(SETUP, ndev, buf); + return count; + +einval: + CTCM_DBF_DEV(SETUP, ndev, "buff_err"); + return -EINVAL; +} + +static void ctcm_print_statistics(struct ctcm_priv *priv) +{ + char *sbuf; + char *p; + + if (!priv) + return; + sbuf = kmalloc(2048, GFP_KERNEL); + if (sbuf == NULL) + return; + p = sbuf; + + p += sprintf(p, " Device FSM state: %s\n", + fsm_getstate_str(priv->fsm)); + p += sprintf(p, " RX channel FSM state: %s\n", + fsm_getstate_str(priv->channel[READ]->fsm)); + p += sprintf(p, " TX channel FSM state: %s\n", + fsm_getstate_str(priv->channel[WRITE]->fsm)); + p += sprintf(p, " Max. TX buffer used: %ld\n", + priv->channel[WRITE]->prof.maxmulti); + p += sprintf(p, " Max. chained SKBs: %ld\n", + priv->channel[WRITE]->prof.maxcqueue); + p += sprintf(p, " TX single write ops: %ld\n", + priv->channel[WRITE]->prof.doios_single); + p += sprintf(p, " TX multi write ops: %ld\n", + priv->channel[WRITE]->prof.doios_multi); + p += sprintf(p, " Netto bytes written: %ld\n", + priv->channel[WRITE]->prof.txlen); + p += sprintf(p, " Max. TX IO-time: %ld\n", + priv->channel[WRITE]->prof.tx_time); + + printk(KERN_INFO "Statistics for %s:\n%s", + priv->channel[WRITE]->netdev->name, sbuf); + kfree(sbuf); + return; +} + +static ssize_t stats_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct ctcm_priv *priv = dev_get_drvdata(dev); + if (!priv) + return -ENODEV; + ctcm_print_statistics(priv); + return sprintf(buf, "0\n"); +} + +static ssize_t stats_write(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct ctcm_priv *priv = dev_get_drvdata(dev); + if (!priv) + return -ENODEV; + /* Reset statistics */ + memset(&priv->channel[WRITE]->prof, 0, + sizeof(priv->channel[WRITE]->prof)); + return count; +} + +static ssize_t ctcm_proto_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct ctcm_priv *priv = dev_get_drvdata(dev); + if (!priv) + return -ENODEV; + + return sprintf(buf, "%d\n", priv->protocol); +} + +static ssize_t ctcm_proto_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int value; + struct ctcm_priv *priv = dev_get_drvdata(dev); + + if (!priv) + return -ENODEV; + sscanf(buf, "%u", &value); + if (!((value == CTCM_PROTO_S390) || + (value == CTCM_PROTO_LINUX) || + (value == CTCM_PROTO_MPC) || + (value == CTCM_PROTO_OS390))) + return -EINVAL; + priv->protocol = value; + CTCM_DBF_DEV(SETUP, dev, buf); + + return count; +} + +static ssize_t ctcm_type_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct ccwgroup_device *cgdev; + + cgdev = to_ccwgroupdev(dev); + if (!cgdev) + return -ENODEV; + + return sprintf(buf, "%s\n", + cu3088_type[cgdev->cdev[0]->id.driver_info]); +} + +static DEVICE_ATTR(buffer, 0644, ctcm_buffer_show, ctcm_buffer_write); +static DEVICE_ATTR(protocol, 0644, ctcm_proto_show, ctcm_proto_store); +static DEVICE_ATTR(type, 0444, ctcm_type_show, NULL); +static DEVICE_ATTR(stats, 0644, stats_show, stats_write); + +static struct attribute *ctcm_attr[] = { + &dev_attr_protocol.attr, + &dev_attr_type.attr, + &dev_attr_buffer.attr, + NULL, +}; + +static struct attribute_group ctcm_attr_group = { + .attrs = ctcm_attr, +}; + +int ctcm_add_attributes(struct device *dev) +{ + int rc; + + rc = device_create_file(dev, &dev_attr_stats); + + return rc; +} + +void ctcm_remove_attributes(struct device *dev) +{ + device_remove_file(dev, &dev_attr_stats); +} + +int ctcm_add_files(struct device *dev) +{ + return sysfs_create_group(&dev->kobj, &ctcm_attr_group); +} + +void ctcm_remove_files(struct device *dev) +{ + sysfs_remove_group(&dev->kobj, &ctcm_attr_group); +} + -- ^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [patch 3/4] ctcm: infrastructure for replaced ctc driver 2008-02-06 18:27 ` [patch 3/4] ctcm: infrastructure for replaced ctc driver Ursula Braun @ 2008-02-07 6:35 ` Christoph Hellwig 2008-02-07 11:52 ` Rick Troth 0 siblings, 1 reply; 8+ messages in thread From: Christoph Hellwig @ 2008-02-07 6:35 UTC (permalink / raw) To: Ursula Braun; +Cc: jgarzik, netdev, linux-s390, Peter Tiedemann On Wed, Feb 06, 2008 at 07:27:26PM +0100, Ursula Braun wrote: > From: Peter Tiedemann <ptiedem@de.ibm.com> > > ctcm driver supports the channel-to-channel connections of the > old ctc driver plus an additional MPC protocol to provide SNA > connectivity. > > This new ctcm driver replaces the existing ctc driver. > > Signed-off-by: Peter Tiedemann <ptiedem@de.ibm.com> > Signed-off-by: Ursula Braun <braunu@de.ibm.com> > --- > > drivers/s390/net/Kconfig | 12 > drivers/s390/net/Makefile | 5 > drivers/s390/net/ctcm_dbug.c | 67 + > drivers/s390/net/ctcm_dbug.h | 158 ++ > drivers/s390/net/ctcm_fsms.c | 2338 +++++++++++++++++++++++++++++++++++++++ > drivers/s390/net/ctcm_fsms.h | 359 ++++++ > drivers/s390/net/ctcm_main.c | 1772 ++++++++++++++++++++++++++++++ > drivers/s390/net/ctcm_main.h | 287 ++++ > drivers/s390/net/ctcm_mpc.c | 2467 ++++++++++++++++++++++++++++++++++++++++++ > drivers/s390/net/ctcm_mpc.h | 239 ++++ > drivers/s390/net/ctcm_sysfs.c | 210 +++ > 11 files changed, 7906 insertions(+), 8 deletions(-) > > Index: linux-2.6-uschi/drivers/s390/net/Makefile > =================================================================== > --- linux-2.6-uschi.orig/drivers/s390/net/Makefile > +++ linux-2.6-uschi/drivers/s390/net/Makefile > @@ -2,11 +2,10 @@ > # S/390 network devices > # > > -ctc-objs := ctcmain.o ctcdbug.o > - > +ctcm-objs := ctcm_main.o ctcm_fsms.o ctcm_mpc.o ctcm_sysfs.o ctcm_dbug.o please don't use foo-objs := but always foo-y += > Index: linux-2.6-uschi/drivers/s390/net/ctcm_dbug.c > =================================================================== > --- /dev/null > +++ linux-2.6-uschi/drivers/s390/net/ctcm_dbug.c > @@ -0,0 +1,67 @@ > +/* > + * drivers/s390/net/ctcm_dbug.c > + * > + * Copyright IBM Corp. 2001, 2007 > + * Authors: Peter Tiedemann (ptiedem@de.ibm.com) > + * > + */ Please don't mention filenames in the top of file comments. On the other hand a little description of what this file does would be quite useful. (This comment applies to all files in this patch) > +#ifdef DEBUG > + #define do_debug 1 > +#else > + #define do_debug 0 > +#endif > +#ifdef DEBUGDATA > + #define do_debug_data 1 > +#else > + #define do_debug_data 0 > +#endif > +#ifdef DEBUGCCW > + #define do_debug_ccw 1 > +#else > + #define do_debug_ccw 0 > +#endif Please don't indent cpp directives with tabs. Normally we don't indent them at all, but if we have to it's just a single space after the # per indentiation level. > +static void chx_rxidle(fsm_instance *fi, int event, void *arg); Please don't put forward declarations in the middle of a file. If you have to have them please put all somewhere at the top of the file, but it would be even nicer to re-order the code to avoid them completely. > +/** > + * 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. > + */ This is not a kerneldoc comment. Please read Documentation/kernel-doc-nano-HOWTO.txt for the correct format, and run it through the extraction script to make sure it's valid. > +static void chx_firstio(fsm_instance *fi, int event, void *arg) > +{ > + struct channel *ch = (struct channel *)arg; no cast needed when converting to/from void * > + struct channel *ch = (struct channel *)arg; > + struct channel *ch2; > + struct net_device *dev = ch->netdev; > + > + CTCM_DBF_DEV_NAME(TRACE, dev, "Got remote disconnect, re-initializing"); > + fsm_deltimer(&ch->timer); > + if (do_debug) > + ctcm_pr_debug("%s: Got remote disconnect, " > + "re-initializing ...\n", dev->name); > + /* > + * Notify device statemachine > + */ > + fsm_event(((struct ctcm_priv *)dev->priv)->fsm, DEV_EVENT_RXDOWN, dev); > + fsm_event(((struct ctcm_priv *)dev->priv)->fsm, DEV_EVENT_TXDOWN, dev); > + > + fsm_newstate(fi, CTC_STATE_DTERM); > + ch2 = ((struct ctcm_priv *)dev->priv)->channel[WRITE]; keeping a local variable of type struct ctcm_priv * would be a lot cleaner than all these casts. This applies to a lot of functions all over this patch. > + fsm_newstate(ch2->fsm, CTC_STATE_DTERM); > + > + ccw_device_halt(ch->cdev, (unsigned long)ch); > + ccw_device_halt(ch2->cdev, (unsigned long)ch2); what an odd calling convention. Why does ccw_device_halt take an unsigned long argument that's actually a pointer? > + header = kzalloc(TH_HEADER_LENGTH, gfp_type()); > + > + if (!header) { > + printk(KERN_WARNING "ctcmpc: OUT OF MEMORY IN %s()" > + ": Data Lost \n", __FUNCTION__); > + spin_unlock(&ch->collect_lock); > + fsm_event(privptr->mpcg->fsm, MPCG_EVENT_INOP, dev); > + goto done; odd indentation messup. > + min((int)ch->trans_skb->len, 50)); please don't case arguments to min but use min_t instead. I gave up here for now, but you should probably run this through checkpatch.pl aswell before resubmitting it. ^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [patch 3/4] ctcm: infrastructure for replaced ctc driver 2008-02-07 6:35 ` Christoph Hellwig @ 2008-02-07 11:52 ` Rick Troth 0 siblings, 0 replies; 8+ messages in thread From: Rick Troth @ 2008-02-07 11:52 UTC (permalink / raw) To: Christoph Hellwig Cc: Ursula Braun, jgarzik, netdev, linux-s390, Peter Tiedemann On Thu, 7 Feb 2008, Christoph Hellwig wrote: ... > > +/* > > + * drivers/s390/net/ctcm_dbug.c > > + * > > + * Copyright IBM Corp. 2001, 2007 > > + * Authors: Peter Tiedemann (ptiedem@de.ibm.com) > > + * > > + */ > > Please don't mention filenames in the top of file comments. > On the other hand a little description of what this file > does would be quite useful. (This comment applies to all > files in this patch) Better descriptions, yes. Of course. But are you saying to NOT name the file in its comments? I am surprised. If I am reading correctly, then I disagree. Is the exclusion of the name of the source file from its comments a stylistic decision the other kernel developers have agreed to? Or is it tied to automation? Or have I (hopefully) misunderstood? -- R; <>< () ascii ribbon campaign - against html e-mail /\ www.asciiribbon.org - against proprietary attachments ^ permalink raw reply [flat|nested] 8+ messages in thread
end of thread, other threads:[~2008-02-07 12:04 UTC | newest] Thread overview: 8+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2008-02-06 11:55 [patch 0/4] s390: ctc patches for 2.6.25 (2nd try) Ursula Braun 2008-02-06 11:55 ` [patch 1/4] ctc / netiucv: consolidate fsm_action_nop Ursula Braun 2008-02-06 11:55 ` [patch 2/4] drivers/s390/net: Kconfig brush up Ursula Braun 2008-02-06 11:55 ` [patch 3/4] ctcm: infrastructure for replaced ctc driver Ursula Braun 2008-02-06 11:55 ` [patch 4/4] ctc: removal of the old " Ursula Braun -- strict thread matches above, loose matches on Subject: below -- 2008-02-06 18:27 [patch 0/4] s390: ctc patches for 2.6.25 (3rd try) Ursula Braun 2008-02-06 18:27 ` [patch 3/4] ctcm: infrastructure for replaced ctc driver Ursula Braun 2008-02-07 6:35 ` Christoph Hellwig 2008-02-07 11:52 ` Rick Troth
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox; as well as URLs for NNTP newsgroup(s).