![Belinda Thompson](/assets/img/avatar_default.png)
Communication Server Linux uses the ctcmpc code of the ctcm driver. Sending problems have shown up caused by a wrong idal word list for the first ccw. Function ctcmpc_chx_txdone() invokes the function to prepare the idal word list without setting an appropriate length for the first ccw, which may lead to an incomplete idal word list. This patch sets the maximum buffer size as data length of the first ccw. Thus correct idal word lists are guaranteed in all cases. Signed-off-by: Belinda Thompson <belindat@us.ibm.com> Reviewed-by: Ursula Braun <ursula.braun@de.ibm.com> Signed-off-by: Frank Blaschka <frank.blaschka@de.ibm.com> Signed-off-by: David S. Miller <davem@davemloft.net>
2307 lines
74 KiB
C
2307 lines
74 KiB
C
/*
|
|
* 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
|
|
|
|
#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 --- */
|
|
|