/* * 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 #define KMSG_COMPONENT "ctcm" #define pr_fmt(fmt) KMSG_COMPONENT ": " fmt #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 "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 void ctcm_action_nop(fsm_instance *fi, int event, void *arg); /* * ----- 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 preceding ccw_device call, halt_IO etc... * * ch : The channel, the error belongs to. * Returns 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, "%s(%s): %s: %04x\n", CTCM_FUNTAIL, ch->id, msg, rc); switch (rc) { case -EBUSY: pr_info("%s: The communication peer is busy\n", ch->id); fsm_event(ch->fsm, CTC_EVENT_IO_EBUSY, ch); break; case -ENODEV: pr_err("%s: The specified target device is not valid\n", ch->id); fsm_event(ch->fsm, CTC_EVENT_IO_ENODEV, ch); break; default: pr_err("An I/O operation resulted in error %04x\n", 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, CTC_DBF_DEBUG, __func__); while ((skb = skb_dequeue(q))) { atomic_dec(&skb->users); dev_kfree_skb_any(skb); } } /** * NOP action for statemachines */ static void ctcm_action_nop(fsm_instance *fi, int event, void *arg) { } /* * Actions for channel - statemachines. */ /** * Normal data has been send. Free the corresponding * skb (it's in io_queue), reset dev->tbusy and * revert to idle state. * * fi An instance of a channel statemachine. * event The event, just happened. * arg Generic pointer, casted from channel * upon call. */ static void chx_txdone(fsm_instance *fi, int event, void *arg) { struct channel *ch = arg; struct net_device *dev = ch->netdev; struct ctcm_priv *priv = dev->ml_priv; struct sk_buff *skb; int first = 1; int i; unsigned long duration; struct timespec done_stamp = current_kernel_time(); /* xtime */ CTCM_PR_DEBUG("%s(%s): %s\n", __func__, ch->id, dev->name); 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.cmd.count != 0) CTCM_DBF_TEXT_(TRACE, CTC_DBF_DEBUG, "%s(%s): TX not complete, remaining %d bytes", CTCM_FUNTAIL, dev->name, ch->irb->scsw.cmd.count); fsm_deltimer(&ch->timer); while ((skb = skb_dequeue(&ch->io_queue))) { priv->stats.tx_packets++; priv->stats.tx_bytes += skb->len - LL_HEADER_LENGTH; if (first) { priv->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); priv->stats.tx_packets++; priv->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) { priv->stats.tx_dropped += i; priv->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. * * fi An instance of a channel statemachine. * event The event, just happened. * arg Generic pointer, casted from channel * upon call. */ void ctcm_chx_txidle(fsm_instance *fi, int event, void *arg) { struct channel *ch = arg; struct net_device *dev = ch->netdev; struct ctcm_priv *priv = dev->ml_priv; CTCM_PR_DEBUG("%s(%s): %s\n", __func__, ch->id, dev->name); fsm_deltimer(&ch->timer); fsm_newstate(fi, CTC_STATE_TXIDLE); fsm_event(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. * * fi An instance of a channel statemachine. * event The event, just happened. * arg Generic pointer, casted from channel * upon call. */ static void chx_rx(fsm_instance *fi, int event, void *arg) { struct channel *ch = arg; struct net_device *dev = ch->netdev; struct ctcm_priv *priv = dev->ml_priv; int len = ch->max_bufsize - ch->irb->scsw.cmd.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_DBF_TEXT_(TRACE, CTC_DBF_NOTICE, "%s(%s): got packet with length %d < 8\n", CTCM_FUNTAIL, dev->name, len); priv->stats.rx_dropped++; priv->stats.rx_length_errors++; goto again; } if (len > ch->max_bufsize) { CTCM_DBF_TEXT_(TRACE, CTC_DBF_NOTICE, "%s(%s): got packet with length %d > %d\n", CTCM_FUNTAIL, dev->name, len, ch->max_bufsize); priv->stats.rx_dropped++; priv->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_DBF_TEXT_(TRACE, CTC_DBF_NOTICE, "%s(%s): got block length %d != rx length %d\n", CTCM_FUNTAIL, dev->name, block_len, len); if (do_debug) ctcmpc_dump_skb(skb, 0); *((__u16 *)skb->data) = len; priv->stats.rx_dropped++; priv->stats.rx_length_errors++; goto again; } if (block_len > 2) { *((__u16 *)skb->data) = block_len - 2; 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"); } /** * Initialize connection by sending a __u16 of value 0. * * fi An instance of a channel statemachine. * event The event, just happened. * arg Generic pointer, casted from channel * upon call. */ static void chx_firstio(fsm_instance *fi, int event, void *arg) { int rc; struct channel *ch = arg; int fsmstate = fsm_getstate(fi); CTCM_DBF_TEXT_(TRACE, CTC_DBF_NOTICE, "%s(%s) : %02x", CTCM_FUNTAIL, ch->id, fsmstate); ch->sense_rc = 0; /* reset unit check report control */ if (fsmstate == CTC_STATE_TXIDLE) CTCM_DBF_TEXT_(TRACE, CTC_DBF_DEBUG, "%s(%s): remote side issued READ?, init.\n", CTCM_FUNTAIL, ch->id); fsm_deltimer(&ch->timer); if (ctcm_checkalloc_buffer(ch)) return; if ((fsmstate == CTC_STATE_SETUPWAIT) && (ch->protocol == CTCM_PROTO_OS390)) { /* OS/390 resp. z/OS */ if (CHANNEL_DIRECTION(ch->flags) == CTCM_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; struct ctcm_priv *priv = dev->ml_priv; fsm_newstate(fi, CTC_STATE_TXIDLE); fsm_event(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) == CTCM_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) == CTCM_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) == CTCM_READ) && (ch->protocol == CTCM_PROTO_S390)) { struct net_device *dev = ch->netdev; struct ctcm_priv *priv = dev->ml_priv; fsm_event(priv->fsm, DEV_EVENT_RXUP, dev); } } /** * Got initial data, check it. If OK, * notify device statemachine that we are up and * running. * * fi An instance of a channel statemachine. * event The event, just happened. * arg Generic pointer, casted from channel * upon call. */ static void chx_rxidle(fsm_instance *fi, int event, void *arg) { struct channel *ch = arg; struct net_device *dev = ch->netdev; struct ctcm_priv *priv = dev->ml_priv; __u16 buflen; int rc; fsm_deltimer(&ch->timer); buflen = *((__u16 *)ch->trans_skb->data); CTCM_PR_DEBUG("%s: %s: Initial RX count = %d\n", __func__, 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(priv->fsm, DEV_EVENT_RXUP, dev); } else { CTCM_PR_DEBUG("%s: %s: Initial RX count %d not %d\n", __func__, dev->name, buflen, CTCM_INITIAL_BLOCKLEN); chx_firstio(fi, event, arg); } } /** * Set channel into extended mode. * * fi An instance of a channel statemachine. * event The event, just happened. * arg Generic pointer, casted from channel * upon call. */ static void ctcm_chx_setmode(fsm_instance *fi, int event, void *arg) { struct channel *ch = arg; int rc; unsigned long saveflags = 0; int timeout = CTCM_TIME_5_SEC; fsm_deltimer(&ch->timer); if (IS_MPC(ch)) { timeout = 1500; CTCM_PR_DEBUG("enter %s: cp=%i ch=0x%p id=%s\n", __func__, smp_processor_id(), ch, ch->id); } fsm_addtimer(&ch->timer, timeout, CTC_EVENT_TIMER, ch); fsm_newstate(fi, CTC_STATE_SETUPWAIT); CTCM_CCW_DUMP((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. * * fi An instance of a channel statemachine. * event The event, just happened. * arg Generic pointer, casted from channel * upon call. */ static void ctcm_chx_start(fsm_instance *fi, int event, void *arg) { struct channel *ch = arg; unsigned long saveflags; int rc; CTCM_DBF_TEXT_(SETUP, CTC_DBF_INFO, "%s(%s): %s", CTCM_FUNTAIL, ch->id, (CHANNEL_DIRECTION(ch->flags) == CTCM_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) == CTCM_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_DBF_TEXT_(TRACE, CTC_DBF_DEBUG, "%s(%s): %s trans_skb alloc delayed " "until first transfer", CTCM_FUNTAIL, ch->id, (CHANNEL_DIRECTION(ch->flags) == CTCM_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. * * fi An instance of a channel statemachine. * event The event, just happened. * arg Generic pointer, casted from channel * upon call. */ static void ctcm_chx_haltio(fsm_instance *fi, int event, void *arg) { struct channel *ch = arg; unsigned long saveflags = 0; int rc; int oldstate; 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 *)__func__); } } } /** * Cleanup helper for chx_fail and chx_stopped * cleanup channels queue and notify interface statemachine. * * fi An instance of a channel statemachine. * state The next state (depending on caller). * 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; struct ctcm_priv *priv = dev->ml_priv; CTCM_DBF_TEXT_(SETUP, CTC_DBF_NOTICE, "%s(%s): %s[%d]\n", CTCM_FUNTAIL, dev->name, ch->id, state); 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) == CTCM_READ) { skb_queue_purge(&ch->io_queue); fsm_event(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(priv->fsm, DEV_EVENT_TXDOWN, dev); } } /** * A channel has successfully been halted. * Cleanup it's queue and notify interface statemachine. * * fi An instance of a channel statemachine. * event The event, just happened. * arg Generic pointer, casted from channel * upon call. */ static void ctcm_chx_stopped(fsm_instance *fi, int event, void *arg) { ctcm_chx_cleanup(fi, CTC_STATE_STOPPED, arg); } /** * A stop command from device statemachine arrived and we are in * not operational mode. Set state to stopped. * * fi An instance of a channel statemachine. * event The event, just happened. * 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. * * fi An instance of a channel statemachine. * event The event, just happened. * arg Generic pointer, casted from channel * upon call. */ static void ctcm_chx_fail(fsm_instance *fi, int event, void *arg) { ctcm_chx_cleanup(fi, CTC_STATE_NOTOP, arg); } /** * Handle error during setup of channel. * * fi An instance of a channel statemachine. * event The event, just happened. * arg Generic pointer, casted from channel * upon call. */ static void ctcm_chx_setuperr(fsm_instance *fi, int event, void *arg) { struct channel *ch = arg; struct net_device *dev = ch->netdev; struct ctcm_priv *priv = dev->ml_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) == CTCM_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) : %s error during %s channel setup state=%s\n", CTCM_FUNTAIL, dev->name, ctc_ch_event_names[event], (CHANNEL_DIRECTION(ch->flags) == CTCM_READ) ? "RX" : "TX", fsm_getstate_str(fi)); if (CHANNEL_DIRECTION(ch->flags) == CTCM_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. * * fi An instance of a channel statemachine. * event The event, just happened. * arg Generic pointer, casted from channel * upon call. */ static void ctcm_chx_restart(fsm_instance *fi, int event, void *arg) { struct channel *ch = arg; struct net_device *dev = ch->netdev; unsigned long saveflags = 0; int oldstate; int rc; CTCM_DBF_TEXT_(TRACE, CTC_DBF_NOTICE, "%s: %s[%d] of %s\n", CTCM_FUNTAIL, ch->id, event, dev->name); fsm_deltimer(&ch->timer); 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) * * fi An instance of a channel statemachine. * event The event, just happened. * arg Generic pointer, casted from channel * upon call. */ static void ctcm_chx_rxiniterr(fsm_instance *fi, int event, void *arg) { struct channel *ch = arg; struct net_device *dev = ch->netdev; struct ctcm_priv *priv = dev->ml_priv; if (event == CTC_EVENT_TIMER) { if (!IS_MPCDEV(dev)) /* TODO : check if MPC deletes timer somewhere */ fsm_deltimer(&ch->timer); if (ch->retry++ < 3) ctcm_chx_restart(fi, event, arg); else { fsm_newstate(fi, CTC_STATE_RXERR); fsm_event(priv->fsm, DEV_EVENT_RXDOWN, dev); } } else { CTCM_DBF_TEXT_(ERROR, CTC_DBF_ERROR, "%s(%s): %s in %s", CTCM_FUNTAIL, ch->id, ctc_ch_event_names[event], fsm_getstate_str(fi)); dev_warn(&dev->dev, "Initialization failed with RX/TX init handshake " "error %s\n", ctc_ch_event_names[event]); } } /** * Notify device statemachine if we gave up initialization * of RX channel. * * fi An instance of a channel statemachine. * event The event, just happened. * arg Generic pointer, casted from channel * upon call. */ static void ctcm_chx_rxinitfail(fsm_instance *fi, int event, void *arg) { struct channel *ch = arg; struct net_device *dev = ch->netdev; struct ctcm_priv *priv = dev->ml_priv; CTCM_DBF_TEXT_(ERROR, CTC_DBF_ERROR, "%s(%s): RX %s busy, init. fail", CTCM_FUNTAIL, dev->name, ch->id); fsm_newstate(fi, CTC_STATE_RXERR); fsm_event(priv->fsm, DEV_EVENT_RXDOWN, dev); } /** * Handle RX Unit check remote reset (remote disconnected) * * fi An instance of a channel statemachine. * event The event, just happened. * arg Generic pointer, casted from channel * upon call. */ static void ctcm_chx_rxdisc(fsm_instance *fi, int event, void *arg) { struct channel *ch = arg; struct channel *ch2; struct net_device *dev = ch->netdev; struct ctcm_priv *priv = dev->ml_priv; CTCM_DBF_TEXT_(TRACE, CTC_DBF_NOTICE, "%s: %s: remote disconnect - re-init ...", CTCM_FUNTAIL, dev->name); fsm_deltimer(&ch->timer); /* * Notify device statemachine */ fsm_event(priv->fsm, DEV_EVENT_RXDOWN, dev); fsm_event(priv->fsm, DEV_EVENT_TXDOWN, dev); fsm_newstate(fi, CTC_STATE_DTERM); ch2 = priv->channel[CTCM_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. * * fi An instance of a channel statemachine. * event The event, just happened. * arg Generic pointer, casted from channel * upon call. */ static void ctcm_chx_txiniterr(fsm_instance *fi, int event, void *arg) { struct channel *ch = arg; struct net_device *dev = ch->netdev; struct ctcm_priv *priv = dev->ml_priv; if (event == CTC_EVENT_TIMER) { fsm_deltimer(&ch->timer); if (ch->retry++ < 3) ctcm_chx_restart(fi, event, arg); else { fsm_newstate(fi, CTC_STATE_TXERR); fsm_event(priv->fsm, DEV_EVENT_TXDOWN, dev); } } else { CTCM_DBF_TEXT_(ERROR, CTC_DBF_ERROR, "%s(%s): %s in %s", CTCM_FUNTAIL, ch->id, ctc_ch_event_names[event], fsm_getstate_str(fi)); dev_warn(&dev->dev, "Initialization failed with RX/TX init handshake " "error %s\n", ctc_ch_event_names[event]); } } /** * Handle TX timeout by retrying operation. * * fi An instance of a channel statemachine. * event The event, just happened. * arg Generic pointer, casted from channel * upon call. */ static void ctcm_chx_txretry(fsm_instance *fi, int event, void *arg) { struct channel *ch = arg; struct net_device *dev = ch->netdev; struct ctcm_priv *priv = dev->ml_priv; struct sk_buff *skb; CTCM_PR_DEBUG("Enter: %s: cp=%i ch=0x%p id=%s\n", __func__, smp_processor_id(), ch, ch->id); fsm_deltimer(&ch->timer); if (ch->retry++ > 3) { struct mpc_group *gptr = priv->mpcg; CTCM_DBF_TEXT_(TRACE, CTC_DBF_INFO, "%s: %s: retries exceeded", CTCM_FUNTAIL, ch->id); 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_DBF_TEXT_(TRACE, CTC_DBF_DEBUG, "%s : %s: retry %d", CTCM_FUNTAIL, ch->id, 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_DBF_TEXT_(TRACE, CTC_DBF_INFO, "%s: %s: IDAL alloc failed", CTCM_FUNTAIL, ch->id); 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. * * fi An instance of a channel statemachine. * event The event, just happened. * arg Generic pointer, casted from channel * upon call. */ static void ctcm_chx_iofatal(fsm_instance *fi, int event, void *arg) { struct channel *ch = arg; struct net_device *dev = ch->netdev; struct ctcm_priv *priv = dev->ml_priv; int rd = CHANNEL_DIRECTION(ch->flags); fsm_deltimer(&ch->timer); CTCM_DBF_TEXT_(ERROR, CTC_DBF_ERROR, "%s: %s: %s unrecoverable channel error", CTCM_FUNTAIL, ch->id, rd == CTCM_READ ? "RX" : "TX"); if (IS_MPC(ch)) { priv->stats.tx_dropped++; priv->stats.tx_errors++; } if (rd == CTCM_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); } } /* * The ctcm statemachine for a channel. */ const fsm_node ch_fsm[] = { { CTC_STATE_STOPPED, CTC_EVENT_STOP, ctcm_action_nop }, { CTC_STATE_STOPPED, CTC_EVENT_START, ctcm_chx_start }, { CTC_STATE_STOPPED, CTC_EVENT_FINSTAT, ctcm_action_nop }, { CTC_STATE_STOPPED, CTC_EVENT_MC_FAIL, ctcm_action_nop }, { CTC_STATE_NOTOP, CTC_EVENT_STOP, ctcm_chx_stop }, { CTC_STATE_NOTOP, CTC_EVENT_START, ctcm_action_nop }, { CTC_STATE_NOTOP, CTC_EVENT_FINSTAT, ctcm_action_nop }, { CTC_STATE_NOTOP, CTC_EVENT_MC_FAIL, ctcm_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, ctcm_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_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, ctcm_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, ctcm_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, ctcm_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, ctcm_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, ctcm_action_nop }, { CTC_STATE_TXIDLE, CTC_EVENT_FINSTAT, chx_firstio }, { CTC_STATE_TXIDLE, CTC_EVENT_UC_RCRESET, ctcm_action_nop }, { CTC_STATE_TXIDLE, CTC_EVENT_UC_RSRESET, ctcm_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, ctcm_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, ctcm_action_nop }, { CTC_STATE_TERM, CTC_EVENT_UC_RSRESET, ctcm_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, ctcm_action_nop }, { CTC_STATE_DTERM, CTC_EVENT_UC_RSRESET, ctcm_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, ctcm_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. * * fi An instance of a channel statemachine. * event The event, just happened. * arg Generic pointer, casted from channel * upon call. */ static void ctcmpc_chx_txdone(fsm_instance *fi, int event, void *arg) { struct channel *ch = arg; struct net_device *dev = ch->netdev; struct ctcm_priv *priv = dev->ml_priv; struct mpc_group *grp = priv->mpcg; struct sk_buff *skb; int first = 1; int i; __u32 data_space; unsigned long duration; struct sk_buff *peekskb; int rc; struct th_header *header; struct pdu *p_header; struct timespec done_stamp = current_kernel_time(); /* xtime */ CTCM_PR_DEBUG("Enter %s: %s cp:%i\n", __func__, dev->name, smp_processor_id()); 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.cmd.count != 0) CTCM_DBF_TEXT_(MPC_TRACE, CTC_DBF_DEBUG, "%s(%s): TX not complete, remaining %d bytes", CTCM_FUNTAIL, dev->name, ch->irb->scsw.cmd.count); fsm_deltimer(&ch->timer); while ((skb = skb_dequeue(&ch->io_queue))) { priv->stats.tx_packets++; priv->stats.tx_bytes += skb->len - TH_HEADER_LENGTH; if (first) { priv->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) || (grp->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; p_header = NULL; data_space = grp->group_max_buflen - TH_HEADER_LENGTH; CTCM_PR_DBGDATA("%s: building trans_skb from collect_q" " data_space:%04x\n", __func__, data_space); 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; CTCM_PR_DBGDATA("%s: trans_skb len:%04x \n", __func__, ch->trans_skb->len); CTCM_PR_DBGDATA("%s: pdu header and data for up" " to 32 bytes sent to vtam\n", __func__); CTCM_D3_DUMP((char *)p_header, min_t(int, skb->len, 32)); ch->collect_len -= skb->len; data_space -= skb->len; priv->stats.tx_packets++; priv->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) { spin_unlock(&ch->collect_lock); fsm_event(priv->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; CTCM_PR_DBGDATA("%s: ToVTAM_th_seq= %08x\n" , __func__, 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); CTCM_PR_DBGDATA("%s: trans_skb len:%04x \n", __func__, ch->trans_skb->len); CTCM_PR_DBGDATA("%s: up-to-50 bytes of trans_skb " "data to vtam from collect_q\n", __func__); CTCM_D3_DUMP((char *)ch->trans_skb->data, min_t(int, ch->trans_skb->len, 50)); spin_unlock(&ch->collect_lock); clear_normalized_cda(&ch->ccw[1]); CTCM_PR_DBGDATA("ccwcda=0x%p data=0x%p\n", (void *)(unsigned long)ch->ccw[1].cda, ch->trans_skb->data); ch->ccw[1].count = ch->max_bufsize; if (set_normalized_cda(&ch->ccw[1], ch->trans_skb->data)) { dev_kfree_skb_any(ch->trans_skb); ch->trans_skb = NULL; CTCM_DBF_TEXT_(MPC_TRACE, CTC_DBF_ERROR, "%s: %s: IDAL alloc failed", CTCM_FUNTAIL, ch->id); fsm_event(priv->mpcg->fsm, MPCG_EVENT_INOP, dev); return; } CTCM_PR_DBGDATA("ccwcda=0x%p data=0x%p\n", (void *)(unsigned long)ch->ccw[1].cda, ch->trans_skb->data); 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) { priv->stats.tx_dropped += i; priv->stats.tx_errors += i; fsm_deltimer(&ch->timer); ctcm_ccw_check_rc(ch, rc, "chained TX"); } done: ctcm_clear_busy(dev); return; } /** * Got normal data, check for sanity, queue it up, allocate new buffer * trigger bottom half, and initiate next read. * * fi An instance of a channel statemachine. * event The event, just happened. * arg Generic pointer, casted from channel * upon call. */ static void ctcmpc_chx_rx(fsm_instance *fi, int event, void *arg) { struct channel *ch = arg; struct net_device *dev = ch->netdev; struct ctcm_priv *priv = dev->ml_priv; struct mpc_group *grp = priv->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.cmd.count; CTCM_PR_DEBUG("%s: %s: cp:%i %s maxbuf : %04x, len: %04x\n", CTCM_FUNTAIL, dev->name, smp_processor_id(), ch->id, ch->max_bufsize, len); fsm_deltimer(&ch->timer); if (skb == NULL) { CTCM_DBF_TEXT_(MPC_ERROR, CTC_DBF_ERROR, "%s(%s): TRANS_SKB = NULL", CTCM_FUNTAIL, dev->name); goto again; } if (len < TH_HEADER_LENGTH) { CTCM_DBF_TEXT_(MPC_ERROR, CTC_DBF_ERROR, "%s(%s): packet length %d to short", CTCM_FUNTAIL, dev->name, len); priv->stats.rx_dropped++; priv->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) { CTCM_DBF_TEXT_(MPC_ERROR, CTC_DBF_ERROR, "%s(%d): skb allocation failed", CTCM_FUNTAIL, dev->name); fsm_event(priv->mpcg->fsm, MPCG_EVENT_INOP, dev); goto again; } switch (fsm_getstate(grp->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(grp->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; } CTCM_PR_DEBUG("Exit %s: %s, ch=0x%p, id=%s\n", __func__, dev->name, ch, ch->id); } /** * Initialize connection by sending a __u16 of value 0. * * fi An instance of a channel statemachine. * event The event, just happened. * arg Generic pointer, casted from channel * upon call. */ static void ctcmpc_chx_firstio(fsm_instance *fi, int event, void *arg) { struct channel *ch = arg; struct net_device *dev = ch->netdev; struct ctcm_priv *priv = dev->ml_priv; struct mpc_group *gptr = priv->mpcg; CTCM_PR_DEBUG("Enter %s: id=%s, ch=0x%p\n", __func__, ch->id, ch); CTCM_DBF_TEXT_(MPC_TRACE, CTC_DBF_INFO, "%s: %s: chstate:%i, grpstate:%i, prot:%i\n", CTCM_FUNTAIL, 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) == CTCM_READ) { ctcmpc_chx_rxidle(fi, event, arg); } else { fsm_newstate(fi, CTC_STATE_TXIDLE); fsm_event(priv->fsm, DEV_EVENT_TXUP, dev); } goto done; default: break; } fsm_newstate(fi, (CHANNEL_DIRECTION(ch->flags) == CTCM_READ) ? CTC_STATE_RXINIT : CTC_STATE_TXINIT); done: CTCM_PR_DEBUG("Exit %s: id=%s, ch=0x%p\n", __func__, ch->id, ch); return; } /** * Got initial data, check it. If OK, * notify device statemachine that we are up and * running. * * fi An instance of a channel statemachine. * event The event, just happened. * arg Generic pointer, casted from channel * upon call. */ void ctcmpc_chx_rxidle(fsm_instance *fi, int event, void *arg) { struct channel *ch = arg; struct net_device *dev = ch->netdev; struct ctcm_priv *priv = dev->ml_priv; struct mpc_group *grp = priv->mpcg; int rc; unsigned long saveflags = 0; /* avoids compiler warning */ fsm_deltimer(&ch->timer); CTCM_PR_DEBUG("%s: %s: %s: cp:%i, chstate:%i grpstate:%i\n", __func__, ch->id, dev->name, smp_processor_id(), fsm_getstate(fi), fsm_getstate(grp->fsm)); fsm_newstate(fi, CTC_STATE_RXIDLE); /* XID processing complete */ switch (fsm_getstate(grp->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; CTCM_CCW_DUMP((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(priv->fsm, DEV_EVENT_RXUP, dev); done: 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 = arg; struct net_device *dev = ch->netdev; struct ctcm_priv *priv = dev->ml_priv; struct mpc_group *grp = priv->mpcg; CTCM_PR_DEBUG("%s(%s): %s(ch=0x%p), cp=%i, ChStat:%s, GrpStat:%s\n", __func__, dev->name, ch->id, ch, smp_processor_id(), fsm_getstate_str(ch->fsm), fsm_getstate_str(grp->fsm)); switch (fsm_getstate(grp->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(&grp->timer); fsm_addtimer(&grp->timer, MPC_XID_TIMEOUT_VALUE, MPCG_EVENT_TIMER, dev); fsm_event(grp->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(grp->fsm, MPCG_EVENT_XID7DONE, dev); break; } 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 = arg; struct net_device *dev = ch->netdev; struct ctcm_priv *priv = dev->ml_priv; struct mpc_group *grp = priv->mpcg; CTCM_PR_DEBUG("%s(%s): %s\n ChState:%s GrpState:%s\n", __func__, dev->name, ch->id, fsm_getstate_str(ch->fsm), fsm_getstate_str(grp->fsm)); fsm_deltimer(&ch->timer); switch (fsm_getstate(grp->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 */ grp->changed_side = 1; fsm_newstate(grp->fsm, MPCG_STATE_XID2INITW); break; case MPCG_STATE_XID2INITW: if (grp->changed_side == 1) { grp->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 (grp->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(&grp->timer); goto done; } fsm_event(grp->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 (grp->changed_side == 1) { grp->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(grp->fsm, MPCG_EVENT_INOP, dev); goto done; } if (grp->changed_side == 1) { fsm_deltimer(&grp->timer); fsm_addtimer(&grp->timer, MPC_XID_TIMEOUT_VALUE, MPCG_EVENT_TIMER, dev); } if (ch->in_mpcgroup) fsm_event(grp->fsm, MPCG_EVENT_XID0DO, ch); else CTCM_DBF_TEXT_(MPC_ERROR, CTC_DBF_ERROR, "%s(%s): channel %s not added to group", CTCM_FUNTAIL, dev->name, ch->id); done: 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 = arg; struct net_device *dev = ch->netdev; struct ctcm_priv *priv = dev->ml_priv; struct mpc_group *grp = priv->mpcg; fsm_event(grp->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 = arg; struct net_device *dev = ach->netdev; struct ctcm_priv *priv = dev->ml_priv; struct mpc_group *grp = priv->mpcg; struct channel *wch = priv->channel[CTCM_WRITE]; struct channel *rch = priv->channel[CTCM_READ]; struct sk_buff *skb; struct th_sweep *header; int rc = 0; unsigned long saveflags = 0; CTCM_PR_DEBUG("ctcmpc enter: %s(): cp=%i ch=0x%p id=%s\n", __func__, smp_processor_id(), ach, ach->id); if (grp->in_sweep == 0) goto done; CTCM_PR_DBGDATA("%s: 1: ToVTAM_th_seq= %08x\n" , __func__, wch->th_seq_num); CTCM_PR_DBGDATA("%s: 1: FromVTAM_th_seq= %08x\n" , __func__, 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)) { grp->in_sweep = 0; ctcm_clear_busy_do(dev); dev_kfree_skb_any(skb); fsm_event(grp->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: grp->sweep_req_pend_num--; break; case TH_SWEEP_RESP: grp->sweep_rsp_pend_num--; break; } header->sw.th_last_seq = wch->th_seq_num; CTCM_CCW_DUMP((char *)&wch->ccw[3], sizeof(struct ccw1) * 3); CTCM_PR_DBGDATA("%s: sweep packet\n", __func__); CTCM_D3_DUMP((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 ((grp->sweep_req_pend_num == 0) && (grp->sweep_rsp_pend_num == 0)) { grp->in_sweep = 0; rch->th_seq_num = 0x00; wch->th_seq_num = 0x00; ctcm_clear_busy_do(dev); } CTCM_PR_DBGDATA("%s: To-/From-VTAM_th_seq = %08x/%08x\n" , __func__, wch->th_seq_num, rch->th_seq_num); if (rc != 0) ctcm_ccw_check_rc(wch, rc, "send sweep"); done: return; } /* * The ctcmpc statemachine for a channel. */ const fsm_node ctcmpc_ch_fsm[] = { { CTC_STATE_STOPPED, CTC_EVENT_STOP, ctcm_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, ctcm_action_nop }, { CTC_STATE_STOPPED, CTC_EVENT_MC_FAIL, ctcm_action_nop }, { CTC_STATE_NOTOP, CTC_EVENT_STOP, ctcm_chx_stop }, { CTC_STATE_NOTOP, CTC_EVENT_START, ctcm_action_nop }, { CTC_STATE_NOTOP, CTC_EVENT_FINSTAT, ctcm_action_nop }, { CTC_STATE_NOTOP, CTC_EVENT_MC_FAIL, ctcm_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, ctcm_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, ctcm_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, ctcm_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, ctcm_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, ctcm_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, ctcm_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, ctcm_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, ctcm_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, ctcm_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, ctcm_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, ctcm_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, ctcm_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, ctcm_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, ctcm_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, ctcm_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, ctcm_action_nop }, { CTC_STATE_TERM, CTC_EVENT_UC_RSRESET, ctcm_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, ctcm_action_nop }, { CTC_STATE_DTERM, CTC_EVENT_UC_RSRESET, ctcm_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, ctcm_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. * * fi An instance of an interface statemachine. * event The event, just happened. * 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 = arg; struct ctcm_priv *priv = dev->ml_priv; int direction; CTCMY_DBF_DEV_NAME(SETUP, dev, ""); fsm_deltimer(&priv->restart_timer); fsm_newstate(fi, DEV_STATE_STARTWAIT_RXTX); if (IS_MPC(priv)) priv->mpcg->channels_terminating = 0; for (direction = CTCM_READ; direction <= CTCM_WRITE; direction++) { struct channel *ch = priv->channel[direction]; fsm_event(ch->fsm, CTC_EVENT_START, ch); } } /** * Shutdown channels by sending CTC_EVENT_STOP to each channel. * * fi An instance of an interface statemachine. * event The event, just happened. * 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 = arg; struct ctcm_priv *priv = dev->ml_priv; CTCMY_DBF_DEV_NAME(SETUP, dev, ""); fsm_newstate(fi, DEV_STATE_STOPWAIT_RXTX); for (direction = CTCM_READ; direction <= CTCM_WRITE; direction++) { struct channel *ch = priv->channel[direction]; fsm_event(ch->fsm, CTC_EVENT_STOP, ch); ch->th_seq_num = 0x00; CTCM_PR_DEBUG("%s: CH_th_seq= %08x\n", __func__, ch->th_seq_num); } if (IS_MPC(priv)) fsm_newstate(priv->mpcg->fsm, MPCG_STATE_RESET); } static void dev_action_restart(fsm_instance *fi, int event, void *arg) { int restart_timer; struct net_device *dev = arg; struct ctcm_priv *priv = dev->ml_priv; CTCMY_DBF_DEV_NAME(TRACE, dev, ""); if (IS_MPC(priv)) { restart_timer = CTCM_TIME_1_SEC; } else { restart_timer = CTCM_TIME_5_SEC; } dev_info(&dev->dev, "Restarting device\n"); dev_action_stop(fi, event, arg); fsm_event(priv->fsm, DEV_EVENT_STOP, dev); if (IS_MPC(priv)) fsm_newstate(priv->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(&priv->restart_timer, restart_timer, DEV_EVENT_START, dev); } /** * Called from channel statemachine * when a channel is up and running. * * fi An instance of an interface statemachine. * event The event, just happened. * 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 = arg; struct ctcm_priv *priv = dev->ml_priv; int dev_stat = fsm_getstate(fi); CTCM_DBF_TEXT_(SETUP, CTC_DBF_NOTICE, "%s(%s): priv = %p [%d,%d]\n ", CTCM_FUNTAIL, dev->name, dev->ml_priv, dev_stat, event); 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); dev_info(&dev->dev, "Connected with remote side\n"); ctcm_clear_busy(dev); } break; case DEV_STATE_STARTWAIT_TX: if (event == DEV_EVENT_TXUP) { fsm_newstate(fi, DEV_STATE_RUNNING); dev_info(&dev->dev, "Connected with remote side\n"); 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(priv)) { if (event == DEV_EVENT_RXUP) mpc_channel_action(priv->channel[CTCM_READ], CTCM_READ, MPC_CHANNEL_ADD); else mpc_channel_action(priv->channel[CTCM_WRITE], CTCM_WRITE, MPC_CHANNEL_ADD); } } /** * Called from device statemachine * when a channel has been shutdown. * * fi An instance of an interface statemachine. * event The event, just happened. * 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 = arg; struct ctcm_priv *priv = dev->ml_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(priv)) { if (event == DEV_EVENT_RXDOWN) mpc_channel_action(priv->channel[CTCM_READ], CTCM_READ, MPC_CHANNEL_REMOVE); else mpc_channel_action(priv->channel[CTCM_WRITE], CTCM_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, ctcm_action_nop }, { DEV_STATE_RUNNING, DEV_EVENT_RXUP, ctcm_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 --- */