diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 18:49:45 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 18:49:45 +0000 |
commit | 2c3c1048746a4622d8c89a29670120dc8fab93c4 (patch) | |
tree | 848558de17fb3008cdf4d861b01ac7781903ce39 /drivers/s390/net/ctcm_main.c | |
parent | Initial commit. (diff) | |
download | linux-2c3c1048746a4622d8c89a29670120dc8fab93c4.tar.xz linux-2c3c1048746a4622d8c89a29670120dc8fab93c4.zip |
Adding upstream version 6.1.76.upstream/6.1.76
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'drivers/s390/net/ctcm_main.c')
-rw-r--r-- | drivers/s390/net/ctcm_main.c | 1781 |
1 files changed, 1781 insertions, 0 deletions
diff --git a/drivers/s390/net/ctcm_main.c b/drivers/s390/net/ctcm_main.c new file mode 100644 index 000000000..bdfab9ea0 --- /dev/null +++ b/drivers/s390/net/ctcm_main.c @@ -0,0 +1,1781 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright IBM Corp. 2001, 2009 + * Author(s): + * Original CTC driver(s): + * Fritz Elfert (felfert@millenux.com) + * Dieter Wellerdiek (wel@de.ibm.com) + * Martin Schwidefsky (schwidefsky@de.ibm.com) + * Denis Joseph Barrow (barrow_dj@yahoo.com) + * Jochen Roehrig (roehrig@de.ibm.com) + * Cornelia Huck <cornelia.huck@de.ibm.com> + * MPC additions: + * Belinda Thompson (belindat@us.ibm.com) + * Andy Richter (richtera@us.ibm.com) + * Revived by: + * Peter Tiedemann (ptiedem@de.ibm.com) + */ + +#undef DEBUG +#undef DEBUGDATA +#undef DEBUGCCW + +#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 "ctcm_fsms.h" +#include "ctcm_main.h" + +/* Some common global variables */ + +/* + * The root device for ctcm group devices + */ +static struct device *ctcm_root_dev; + +/* + * Linked list of all detected channels. + */ +struct channel *channels; + +/* + * Unpack a just received skb and hand it over to + * upper layers. + * + * ch The channel where this skb has been received. + * pskb The received skb. + */ +void ctcm_unpack_skb(struct channel *ch, struct sk_buff *pskb) +{ + struct net_device *dev = ch->netdev; + struct ctcm_priv *priv = dev->ml_priv; + __u16 len = *((__u16 *) pskb->data); + + skb_put(pskb, 2 + LL_HEADER_LENGTH); + skb_pull(pskb, 2); + pskb->dev = dev; + pskb->ip_summed = CHECKSUM_UNNECESSARY; + while (len > 0) { + struct sk_buff *skb; + int skblen; + struct ll_header *header = (struct ll_header *)pskb->data; + + skb_pull(pskb, LL_HEADER_LENGTH); + if ((ch->protocol == CTCM_PROTO_S390) && + (header->type != ETH_P_IP)) { + if (!(ch->logflags & LOG_FLAG_ILLEGALPKT)) { + ch->logflags |= LOG_FLAG_ILLEGALPKT; + /* + * Check packet type only if we stick strictly + * to S/390's protocol of OS390. This only + * supports IP. Otherwise allow any packet + * type. + */ + CTCM_DBF_TEXT_(ERROR, CTC_DBF_ERROR, + "%s(%s): Illegal packet type 0x%04x" + " - dropping", + CTCM_FUNTAIL, dev->name, header->type); + } + priv->stats.rx_dropped++; + priv->stats.rx_frame_errors++; + return; + } + pskb->protocol = cpu_to_be16(header->type); + if ((header->length <= LL_HEADER_LENGTH) || + (len <= LL_HEADER_LENGTH)) { + if (!(ch->logflags & LOG_FLAG_ILLEGALSIZE)) { + CTCM_DBF_TEXT_(ERROR, CTC_DBF_ERROR, + "%s(%s): Illegal packet size %d(%d,%d)" + "- dropping", + CTCM_FUNTAIL, dev->name, + header->length, dev->mtu, len); + ch->logflags |= LOG_FLAG_ILLEGALSIZE; + } + + priv->stats.rx_dropped++; + priv->stats.rx_length_errors++; + return; + } + header->length -= LL_HEADER_LENGTH; + len -= LL_HEADER_LENGTH; + if ((header->length > skb_tailroom(pskb)) || + (header->length > len)) { + if (!(ch->logflags & LOG_FLAG_OVERRUN)) { + CTCM_DBF_TEXT_(ERROR, CTC_DBF_ERROR, + "%s(%s): Packet size %d (overrun)" + " - dropping", CTCM_FUNTAIL, + dev->name, header->length); + ch->logflags |= LOG_FLAG_OVERRUN; + } + + priv->stats.rx_dropped++; + priv->stats.rx_length_errors++; + return; + } + skb_put(pskb, header->length); + skb_reset_mac_header(pskb); + len -= header->length; + skb = dev_alloc_skb(pskb->len); + if (!skb) { + if (!(ch->logflags & LOG_FLAG_NOMEM)) { + CTCM_DBF_TEXT_(ERROR, CTC_DBF_ERROR, + "%s(%s): MEMORY allocation error", + CTCM_FUNTAIL, dev->name); + ch->logflags |= LOG_FLAG_NOMEM; + } + priv->stats.rx_dropped++; + return; + } + skb_copy_from_linear_data(pskb, skb_put(skb, pskb->len), + pskb->len); + skb_reset_mac_header(skb); + skb->dev = pskb->dev; + skb->protocol = pskb->protocol; + pskb->ip_summed = CHECKSUM_UNNECESSARY; + skblen = skb->len; + /* + * reset logflags + */ + ch->logflags = 0; + priv->stats.rx_packets++; + priv->stats.rx_bytes += skblen; + netif_rx(skb); + if (len > 0) { + skb_pull(pskb, header->length); + if (skb_tailroom(pskb) < LL_HEADER_LENGTH) { + CTCM_DBF_DEV_NAME(TRACE, dev, + "Overrun in ctcm_unpack_skb"); + ch->logflags |= LOG_FLAG_OVERRUN; + return; + } + skb_put(pskb, LL_HEADER_LENGTH); + } + } +} + +/* + * Release a specific channel in the channel list. + * + * ch Pointer to channel struct to be released. + */ +static void channel_free(struct channel *ch) +{ + CTCM_DBF_TEXT_(SETUP, CTC_DBF_INFO, "%s(%s)", CTCM_FUNTAIL, ch->id); + ch->flags &= ~CHANNEL_FLAGS_INUSE; + fsm_newstate(ch->fsm, CTC_STATE_IDLE); +} + +/* + * Remove a specific channel in the channel list. + * + * ch Pointer to channel struct to be released. + */ +static void channel_remove(struct channel *ch) +{ + struct channel **c = &channels; + char chid[CTCM_ID_SIZE+1]; + int ok = 0; + + if (ch == NULL) + return; + else + strncpy(chid, ch->id, CTCM_ID_SIZE); + + channel_free(ch); + while (*c) { + if (*c == ch) { + *c = ch->next; + fsm_deltimer(&ch->timer); + if (IS_MPC(ch)) + fsm_deltimer(&ch->sweep_timer); + + kfree_fsm(ch->fsm); + clear_normalized_cda(&ch->ccw[4]); + if (ch->trans_skb != NULL) { + clear_normalized_cda(&ch->ccw[1]); + dev_kfree_skb_any(ch->trans_skb); + } + if (IS_MPC(ch)) { + tasklet_kill(&ch->ch_tasklet); + tasklet_kill(&ch->ch_disc_tasklet); + kfree(ch->discontact_th); + } + kfree(ch->ccw); + kfree(ch->irb); + kfree(ch); + ok = 1; + break; + } + c = &((*c)->next); + } + + CTCM_DBF_TEXT_(SETUP, CTC_DBF_INFO, "%s(%s) %s", CTCM_FUNTAIL, + chid, ok ? "OK" : "failed"); +} + +/* + * Get a specific channel from the channel list. + * + * type Type of channel we are interested in. + * id Id of channel we are interested in. + * direction Direction we want to use this channel for. + * + * returns Pointer to a channel or NULL if no matching channel available. + */ +static struct channel *channel_get(enum ctcm_channel_types type, + char *id, int direction) +{ + struct channel *ch = channels; + + while (ch && (strncmp(ch->id, id, CTCM_ID_SIZE) || (ch->type != type))) + ch = ch->next; + if (!ch) { + CTCM_DBF_TEXT_(ERROR, CTC_DBF_ERROR, + "%s(%d, %s, %d) not found in channel list\n", + CTCM_FUNTAIL, type, id, direction); + } else { + if (ch->flags & CHANNEL_FLAGS_INUSE) + ch = NULL; + else { + ch->flags |= CHANNEL_FLAGS_INUSE; + ch->flags &= ~CHANNEL_FLAGS_RWMASK; + ch->flags |= (direction == CTCM_WRITE) + ? CHANNEL_FLAGS_WRITE : CHANNEL_FLAGS_READ; + fsm_newstate(ch->fsm, CTC_STATE_STOPPED); + } + } + return ch; +} + +static long ctcm_check_irb_error(struct ccw_device *cdev, struct irb *irb) +{ + if (!IS_ERR(irb)) + return 0; + + CTCM_DBF_TEXT_(ERROR, CTC_DBF_WARN, + "irb error %ld on device %s\n", + PTR_ERR(irb), dev_name(&cdev->dev)); + + switch (PTR_ERR(irb)) { + case -EIO: + dev_err(&cdev->dev, + "An I/O-error occurred on the CTCM device\n"); + break; + case -ETIMEDOUT: + dev_err(&cdev->dev, + "An adapter hardware operation timed out\n"); + break; + default: + dev_err(&cdev->dev, + "An error occurred on the adapter hardware\n"); + } + return PTR_ERR(irb); +} + + +/* + * Check sense of a unit check. + * + * ch The channel, the sense code belongs to. + * sense The sense code to inspect. + */ +static void ccw_unit_check(struct channel *ch, __u8 sense) +{ + CTCM_DBF_TEXT_(TRACE, CTC_DBF_DEBUG, + "%s(%s): %02x", + CTCM_FUNTAIL, ch->id, sense); + + if (sense & SNS0_INTERVENTION_REQ) { + if (sense & 0x01) { + if (ch->sense_rc != 0x01) { + pr_notice( + "%s: The communication peer has " + "disconnected\n", ch->id); + ch->sense_rc = 0x01; + } + fsm_event(ch->fsm, CTC_EVENT_UC_RCRESET, ch); + } else { + if (ch->sense_rc != SNS0_INTERVENTION_REQ) { + pr_notice( + "%s: The remote operating system is " + "not available\n", ch->id); + ch->sense_rc = SNS0_INTERVENTION_REQ; + } + fsm_event(ch->fsm, CTC_EVENT_UC_RSRESET, ch); + } + } else if (sense & SNS0_EQUIPMENT_CHECK) { + if (sense & SNS0_BUS_OUT_CHECK) { + if (ch->sense_rc != SNS0_BUS_OUT_CHECK) { + CTCM_DBF_TEXT_(TRACE, CTC_DBF_WARN, + "%s(%s): remote HW error %02x", + CTCM_FUNTAIL, ch->id, sense); + ch->sense_rc = SNS0_BUS_OUT_CHECK; + } + fsm_event(ch->fsm, CTC_EVENT_UC_HWFAIL, ch); + } else { + if (ch->sense_rc != SNS0_EQUIPMENT_CHECK) { + CTCM_DBF_TEXT_(TRACE, CTC_DBF_WARN, + "%s(%s): remote read parity error %02x", + CTCM_FUNTAIL, ch->id, sense); + ch->sense_rc = SNS0_EQUIPMENT_CHECK; + } + fsm_event(ch->fsm, CTC_EVENT_UC_RXPARITY, ch); + } + } else if (sense & SNS0_BUS_OUT_CHECK) { + if (ch->sense_rc != SNS0_BUS_OUT_CHECK) { + CTCM_DBF_TEXT_(TRACE, CTC_DBF_WARN, + "%s(%s): BUS OUT error %02x", + CTCM_FUNTAIL, ch->id, sense); + ch->sense_rc = SNS0_BUS_OUT_CHECK; + } + if (sense & 0x04) /* data-streaming timeout */ + fsm_event(ch->fsm, CTC_EVENT_UC_TXTIMEOUT, ch); + else /* Data-transfer parity error */ + fsm_event(ch->fsm, CTC_EVENT_UC_TXPARITY, ch); + } else if (sense & SNS0_CMD_REJECT) { + if (ch->sense_rc != SNS0_CMD_REJECT) { + CTCM_DBF_TEXT_(TRACE, CTC_DBF_WARN, + "%s(%s): Command rejected", + CTCM_FUNTAIL, ch->id); + ch->sense_rc = SNS0_CMD_REJECT; + } + } else if (sense == 0) { + CTCM_DBF_TEXT_(TRACE, CTC_DBF_WARN, + "%s(%s): Unit check ZERO", + CTCM_FUNTAIL, ch->id); + fsm_event(ch->fsm, CTC_EVENT_UC_ZERO, ch); + } else { + CTCM_DBF_TEXT_(TRACE, CTC_DBF_WARN, + "%s(%s): Unit check code %02x unknown", + CTCM_FUNTAIL, ch->id, sense); + fsm_event(ch->fsm, CTC_EVENT_UC_UNKNOWN, ch); + } +} + +int ctcm_ch_alloc_buffer(struct channel *ch) +{ + clear_normalized_cda(&ch->ccw[1]); + ch->trans_skb = __dev_alloc_skb(ch->max_bufsize, GFP_ATOMIC | GFP_DMA); + if (ch->trans_skb == NULL) { + CTCM_DBF_TEXT_(ERROR, CTC_DBF_ERROR, + "%s(%s): %s trans_skb allocation error", + CTCM_FUNTAIL, ch->id, + (CHANNEL_DIRECTION(ch->flags) == CTCM_READ) ? + "RX" : "TX"); + return -ENOMEM; + } + + ch->ccw[1].count = ch->max_bufsize; + if (set_normalized_cda(&ch->ccw[1], ch->trans_skb->data)) { + dev_kfree_skb(ch->trans_skb); + ch->trans_skb = NULL; + CTCM_DBF_TEXT_(ERROR, CTC_DBF_ERROR, + "%s(%s): %s set norm_cda failed", + CTCM_FUNTAIL, ch->id, + (CHANNEL_DIRECTION(ch->flags) == CTCM_READ) ? + "RX" : "TX"); + return -ENOMEM; + } + + ch->ccw[1].count = 0; + ch->trans_skb_data = ch->trans_skb->data; + ch->flags &= ~CHANNEL_FLAGS_BUFSIZE_CHANGED; + return 0; +} + +/* + * Interface API for upper network layers + */ + +/* + * Open an interface. + * Called from generic network layer when ifconfig up is run. + * + * dev Pointer to interface struct. + * + * returns 0 on success, -ERRNO on failure. (Never fails.) + */ +int ctcm_open(struct net_device *dev) +{ + struct ctcm_priv *priv = dev->ml_priv; + + CTCMY_DBF_DEV_NAME(SETUP, dev, ""); + if (!IS_MPC(priv)) + fsm_event(priv->fsm, DEV_EVENT_START, dev); + return 0; +} + +/* + * Close an interface. + * Called from generic network layer when ifconfig down is run. + * + * dev Pointer to interface struct. + * + * returns 0 on success, -ERRNO on failure. (Never fails.) + */ +int ctcm_close(struct net_device *dev) +{ + struct ctcm_priv *priv = dev->ml_priv; + + CTCMY_DBF_DEV_NAME(SETUP, dev, ""); + if (!IS_MPC(priv)) + fsm_event(priv->fsm, DEV_EVENT_STOP, dev); + return 0; +} + + +/* + * Transmit a packet. + * This is a helper function for ctcm_tx(). + * + * ch Channel to be used for sending. + * skb Pointer to struct sk_buff of packet to send. + * The linklevel header has already been set up + * by ctcm_tx(). + * + * returns 0 on success, -ERRNO on failure. (Never fails.) + */ +static int ctcm_transmit_skb(struct channel *ch, struct sk_buff *skb) +{ + unsigned long saveflags; + struct ll_header header; + int rc = 0; + __u16 block_len; + int ccw_idx; + struct sk_buff *nskb; + unsigned long hi; + + /* we need to acquire the lock for testing the state + * otherwise we can have an IRQ changing the state to + * TXIDLE after the test but before acquiring the lock. + */ + spin_lock_irqsave(&ch->collect_lock, saveflags); + if (fsm_getstate(ch->fsm) != CTC_STATE_TXIDLE) { + int l = skb->len + LL_HEADER_LENGTH; + + if (ch->collect_len + l > ch->max_bufsize - 2) { + spin_unlock_irqrestore(&ch->collect_lock, saveflags); + return -EBUSY; + } else { + refcount_inc(&skb->users); + header.length = l; + header.type = be16_to_cpu(skb->protocol); + header.unused = 0; + memcpy(skb_push(skb, LL_HEADER_LENGTH), &header, + LL_HEADER_LENGTH); + skb_queue_tail(&ch->collect_queue, skb); + ch->collect_len += l; + } + spin_unlock_irqrestore(&ch->collect_lock, saveflags); + goto done; + } + spin_unlock_irqrestore(&ch->collect_lock, saveflags); + /* + * Protect skb against beeing free'd by upper + * layers. + */ + refcount_inc(&skb->users); + ch->prof.txlen += skb->len; + header.length = skb->len + LL_HEADER_LENGTH; + header.type = be16_to_cpu(skb->protocol); + header.unused = 0; + memcpy(skb_push(skb, LL_HEADER_LENGTH), &header, LL_HEADER_LENGTH); + block_len = skb->len + 2; + *((__u16 *)skb_push(skb, 2)) = block_len; + + /* + * IDAL support in CTCM is broken, so we have to + * care about skb's above 2G ourselves. + */ + hi = ((unsigned long)skb_tail_pointer(skb) + LL_HEADER_LENGTH) >> 31; + if (hi) { + nskb = alloc_skb(skb->len, GFP_ATOMIC | GFP_DMA); + if (!nskb) { + refcount_dec(&skb->users); + skb_pull(skb, LL_HEADER_LENGTH + 2); + ctcm_clear_busy(ch->netdev); + return -ENOMEM; + } else { + skb_put_data(nskb, skb->data, skb->len); + refcount_inc(&nskb->users); + refcount_dec(&skb->users); + dev_kfree_skb_irq(skb); + skb = nskb; + } + } + + ch->ccw[4].count = block_len; + if (set_normalized_cda(&ch->ccw[4], skb->data)) { + /* + * idal allocation failed, try via copying to + * trans_skb. trans_skb usually has a pre-allocated + * idal. + */ + if (ctcm_checkalloc_buffer(ch)) { + /* + * Remove our header. It gets added + * again on retransmit. + */ + refcount_dec(&skb->users); + skb_pull(skb, LL_HEADER_LENGTH + 2); + ctcm_clear_busy(ch->netdev); + return -ENOMEM; + } + + skb_reset_tail_pointer(ch->trans_skb); + ch->trans_skb->len = 0; + ch->ccw[1].count = skb->len; + skb_copy_from_linear_data(skb, + skb_put(ch->trans_skb, skb->len), skb->len); + refcount_dec(&skb->users); + dev_kfree_skb_irq(skb); + ccw_idx = 0; + } else { + skb_queue_tail(&ch->io_queue, skb); + ccw_idx = 3; + } + if (do_debug_ccw) + ctcmpc_dumpit((char *)&ch->ccw[ccw_idx], + sizeof(struct ccw1) * 3); + ch->retry = 0; + fsm_newstate(ch->fsm, CTC_STATE_TX); + fsm_addtimer(&ch->timer, CTCM_TIME_5_SEC, CTC_EVENT_TIMER, ch); + spin_lock_irqsave(get_ccwdev_lock(ch->cdev), saveflags); + ch->prof.send_stamp = jiffies; + rc = ccw_device_start(ch->cdev, &ch->ccw[ccw_idx], 0, 0xff, 0); + spin_unlock_irqrestore(get_ccwdev_lock(ch->cdev), saveflags); + if (ccw_idx == 3) + ch->prof.doios_single++; + if (rc != 0) { + fsm_deltimer(&ch->timer); + ctcm_ccw_check_rc(ch, rc, "single skb TX"); + if (ccw_idx == 3) + skb_dequeue_tail(&ch->io_queue); + /* + * Remove our header. It gets added + * again on retransmit. + */ + skb_pull(skb, LL_HEADER_LENGTH + 2); + } else if (ccw_idx == 0) { + struct net_device *dev = ch->netdev; + struct ctcm_priv *priv = dev->ml_priv; + priv->stats.tx_packets++; + priv->stats.tx_bytes += skb->len - LL_HEADER_LENGTH; + } +done: + ctcm_clear_busy(ch->netdev); + return rc; +} + +static void ctcmpc_send_sweep_req(struct channel *rch) +{ + struct net_device *dev = rch->netdev; + struct ctcm_priv *priv; + struct mpc_group *grp; + struct th_sweep *header; + struct sk_buff *sweep_skb; + struct channel *ch; + /* int rc = 0; */ + + priv = dev->ml_priv; + grp = priv->mpcg; + ch = priv->channel[CTCM_WRITE]; + + /* sweep processing is not complete until response and request */ + /* has completed for all read channels in group */ + if (grp->in_sweep == 0) { + grp->in_sweep = 1; + grp->sweep_rsp_pend_num = grp->active_channels[CTCM_READ]; + grp->sweep_req_pend_num = grp->active_channels[CTCM_READ]; + } + + sweep_skb = __dev_alloc_skb(MPC_BUFSIZE_DEFAULT, GFP_ATOMIC|GFP_DMA); + + if (sweep_skb == NULL) { + /* rc = -ENOMEM; */ + goto nomem; + } + + header = skb_put_zero(sweep_skb, TH_SWEEP_LENGTH); + header->th.th_ch_flag = TH_SWEEP_REQ; /* 0x0f */ + header->sw.th_last_seq = ch->th_seq_num; + + netif_trans_update(dev); + skb_queue_tail(&ch->sweep_queue, sweep_skb); + + fsm_addtimer(&ch->sweep_timer, 100, CTC_EVENT_RSWEEP_TIMER, ch); + + return; + +nomem: + grp->in_sweep = 0; + ctcm_clear_busy(dev); + fsm_event(grp->fsm, MPCG_EVENT_INOP, dev); + + return; +} + +/* + * MPC mode version of transmit_skb + */ +static int ctcmpc_transmit_skb(struct channel *ch, struct sk_buff *skb) +{ + struct pdu *p_header; + struct net_device *dev = ch->netdev; + struct ctcm_priv *priv = dev->ml_priv; + struct mpc_group *grp = priv->mpcg; + struct th_header *header; + struct sk_buff *nskb; + int rc = 0; + int ccw_idx; + unsigned long hi; + unsigned long saveflags = 0; /* avoids compiler warning */ + + CTCM_PR_DEBUG("Enter %s: %s, cp=%i ch=0x%p id=%s state=%s\n", + __func__, dev->name, smp_processor_id(), ch, + ch->id, fsm_getstate_str(ch->fsm)); + + if ((fsm_getstate(ch->fsm) != CTC_STATE_TXIDLE) || grp->in_sweep) { + spin_lock_irqsave(&ch->collect_lock, saveflags); + refcount_inc(&skb->users); + + p_header = skb_push(skb, PDU_HEADER_LENGTH); + p_header->pdu_offset = skb->len - PDU_HEADER_LENGTH; + p_header->pdu_proto = 0x01; + if (be16_to_cpu(skb->protocol) == ETH_P_SNAP) { + p_header->pdu_flag = PDU_FIRST | PDU_CNTL; + } else { + p_header->pdu_flag = PDU_FIRST; + } + p_header->pdu_seq = 0; + + CTCM_PR_DEBUG("%s(%s): Put on collect_q - skb len: %04x \n" + "pdu header and data for up to 32 bytes:\n", + __func__, dev->name, skb->len); + CTCM_D3_DUMP((char *)skb->data, min_t(int, 32, skb->len)); + + skb_queue_tail(&ch->collect_queue, skb); + ch->collect_len += skb->len; + + spin_unlock_irqrestore(&ch->collect_lock, saveflags); + goto done; + } + + /* + * Protect skb against beeing free'd by upper + * layers. + */ + refcount_inc(&skb->users); + + /* + * IDAL support in CTCM is broken, so we have to + * care about skb's above 2G ourselves. + */ + hi = ((unsigned long)skb->tail + TH_HEADER_LENGTH) >> 31; + if (hi) { + nskb = __dev_alloc_skb(skb->len, GFP_ATOMIC | GFP_DMA); + if (!nskb) { + goto nomem_exit; + } else { + skb_put_data(nskb, skb->data, skb->len); + refcount_inc(&nskb->users); + refcount_dec(&skb->users); + dev_kfree_skb_irq(skb); + skb = nskb; + } + } + + p_header = skb_push(skb, PDU_HEADER_LENGTH); + p_header->pdu_offset = skb->len - PDU_HEADER_LENGTH; + p_header->pdu_proto = 0x01; + p_header->pdu_seq = 0; + if (be16_to_cpu(skb->protocol) == ETH_P_SNAP) { + p_header->pdu_flag = PDU_FIRST | PDU_CNTL; + } else { + p_header->pdu_flag = PDU_FIRST; + } + + if (ch->collect_len > 0) { + spin_lock_irqsave(&ch->collect_lock, saveflags); + skb_queue_tail(&ch->collect_queue, skb); + ch->collect_len += skb->len; + skb = skb_dequeue(&ch->collect_queue); + ch->collect_len -= skb->len; + spin_unlock_irqrestore(&ch->collect_lock, saveflags); + } + + p_header = (struct pdu *)skb->data; + p_header->pdu_flag |= PDU_LAST; + + ch->prof.txlen += skb->len - PDU_HEADER_LENGTH; + + /* put the TH on the packet */ + header = skb_push(skb, TH_HEADER_LENGTH); + memset(header, 0, TH_HEADER_LENGTH); + + 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(%s) ToVTAM_th_seq= %08x\n" , + __func__, dev->name, ch->th_seq_num); + + CTCM_PR_DBGDATA("%s(%s): skb len: %04x\n - pdu header and data for " + "up to 32 bytes sent to vtam:\n", + __func__, dev->name, skb->len); + CTCM_D3_DUMP((char *)skb->data, min_t(int, 32, skb->len)); + + ch->ccw[4].count = skb->len; + if (set_normalized_cda(&ch->ccw[4], skb->data)) { + /* + * idal allocation failed, try via copying to trans_skb. + * trans_skb usually has a pre-allocated idal. + */ + if (ctcm_checkalloc_buffer(ch)) { + /* + * Remove our header. + * It gets added again on retransmit. + */ + goto nomem_exit; + } + + skb_reset_tail_pointer(ch->trans_skb); + ch->trans_skb->len = 0; + ch->ccw[1].count = skb->len; + skb_put_data(ch->trans_skb, skb->data, skb->len); + refcount_dec(&skb->users); + dev_kfree_skb_irq(skb); + ccw_idx = 0; + CTCM_PR_DBGDATA("%s(%s): trans_skb len: %04x\n" + "up to 32 bytes sent to vtam:\n", + __func__, dev->name, ch->trans_skb->len); + CTCM_D3_DUMP((char *)ch->trans_skb->data, + min_t(int, 32, ch->trans_skb->len)); + } else { + skb_queue_tail(&ch->io_queue, skb); + ccw_idx = 3; + } + ch->retry = 0; + fsm_newstate(ch->fsm, CTC_STATE_TX); + fsm_addtimer(&ch->timer, CTCM_TIME_5_SEC, CTC_EVENT_TIMER, ch); + + if (do_debug_ccw) + ctcmpc_dumpit((char *)&ch->ccw[ccw_idx], + sizeof(struct ccw1) * 3); + + spin_lock_irqsave(get_ccwdev_lock(ch->cdev), saveflags); + ch->prof.send_stamp = jiffies; + rc = ccw_device_start(ch->cdev, &ch->ccw[ccw_idx], 0, 0xff, 0); + spin_unlock_irqrestore(get_ccwdev_lock(ch->cdev), saveflags); + if (ccw_idx == 3) + ch->prof.doios_single++; + if (rc != 0) { + fsm_deltimer(&ch->timer); + ctcm_ccw_check_rc(ch, rc, "single skb TX"); + if (ccw_idx == 3) + skb_dequeue_tail(&ch->io_queue); + } else if (ccw_idx == 0) { + priv->stats.tx_packets++; + priv->stats.tx_bytes += skb->len - TH_HEADER_LENGTH; + } + if (ch->th_seq_num > 0xf0000000) /* Chose at random. */ + ctcmpc_send_sweep_req(ch); + + goto done; +nomem_exit: + CTCM_DBF_TEXT_(MPC_ERROR, CTC_DBF_CRIT, + "%s(%s): MEMORY allocation ERROR\n", + CTCM_FUNTAIL, ch->id); + rc = -ENOMEM; + refcount_dec(&skb->users); + dev_kfree_skb_any(skb); + fsm_event(priv->mpcg->fsm, MPCG_EVENT_INOP, dev); +done: + CTCM_PR_DEBUG("Exit %s(%s)\n", __func__, dev->name); + return rc; +} + +/* + * Start transmission of a packet. + * Called from generic network device layer. + */ +/* first merge version - leaving both functions separated */ +static netdev_tx_t ctcm_tx(struct sk_buff *skb, struct net_device *dev) +{ + struct ctcm_priv *priv = dev->ml_priv; + + if (skb == NULL) { + CTCM_DBF_TEXT_(ERROR, CTC_DBF_ERROR, + "%s(%s): NULL sk_buff passed", + CTCM_FUNTAIL, dev->name); + priv->stats.tx_dropped++; + return NETDEV_TX_OK; + } + if (skb_headroom(skb) < (LL_HEADER_LENGTH + 2)) { + CTCM_DBF_TEXT_(ERROR, CTC_DBF_ERROR, + "%s(%s): Got sk_buff with head room < %ld bytes", + CTCM_FUNTAIL, dev->name, LL_HEADER_LENGTH + 2); + dev_kfree_skb(skb); + priv->stats.tx_dropped++; + return NETDEV_TX_OK; + } + + /* + * If channels are not running, try to restart them + * and throw away packet. + */ + if (fsm_getstate(priv->fsm) != DEV_STATE_RUNNING) { + fsm_event(priv->fsm, DEV_EVENT_START, dev); + dev_kfree_skb(skb); + priv->stats.tx_dropped++; + priv->stats.tx_errors++; + priv->stats.tx_carrier_errors++; + return NETDEV_TX_OK; + } + + if (ctcm_test_and_set_busy(dev)) + return NETDEV_TX_BUSY; + + netif_trans_update(dev); + if (ctcm_transmit_skb(priv->channel[CTCM_WRITE], skb) != 0) + return NETDEV_TX_BUSY; + return NETDEV_TX_OK; +} + +/* unmerged MPC variant of ctcm_tx */ +static netdev_tx_t ctcmpc_tx(struct sk_buff *skb, struct net_device *dev) +{ + int len = 0; + struct ctcm_priv *priv = dev->ml_priv; + struct mpc_group *grp = priv->mpcg; + struct sk_buff *newskb = NULL; + + /* + * Some sanity checks ... + */ + if (skb == NULL) { + CTCM_DBF_TEXT_(MPC_ERROR, CTC_DBF_ERROR, + "%s(%s): NULL sk_buff passed", + CTCM_FUNTAIL, dev->name); + priv->stats.tx_dropped++; + goto done; + } + if (skb_headroom(skb) < (TH_HEADER_LENGTH + PDU_HEADER_LENGTH)) { + CTCM_DBF_TEXT_(MPC_TRACE, CTC_DBF_ERROR, + "%s(%s): Got sk_buff with head room < %ld bytes", + CTCM_FUNTAIL, dev->name, + TH_HEADER_LENGTH + PDU_HEADER_LENGTH); + + CTCM_D3_DUMP((char *)skb->data, min_t(int, 32, skb->len)); + + len = skb->len + TH_HEADER_LENGTH + PDU_HEADER_LENGTH; + newskb = __dev_alloc_skb(len, GFP_ATOMIC | GFP_DMA); + + if (!newskb) { + CTCM_DBF_TEXT_(MPC_TRACE, CTC_DBF_ERROR, + "%s: %s: __dev_alloc_skb failed", + __func__, dev->name); + + dev_kfree_skb_any(skb); + priv->stats.tx_dropped++; + priv->stats.tx_errors++; + priv->stats.tx_carrier_errors++; + fsm_event(grp->fsm, MPCG_EVENT_INOP, dev); + goto done; + } + newskb->protocol = skb->protocol; + skb_reserve(newskb, TH_HEADER_LENGTH + PDU_HEADER_LENGTH); + skb_put_data(newskb, skb->data, skb->len); + dev_kfree_skb_any(skb); + skb = newskb; + } + + /* + * If channels are not running, + * notify anybody about a link failure and throw + * away packet. + */ + if ((fsm_getstate(priv->fsm) != DEV_STATE_RUNNING) || + (fsm_getstate(grp->fsm) < MPCG_STATE_XID2INITW)) { + dev_kfree_skb_any(skb); + CTCM_DBF_TEXT_(MPC_ERROR, CTC_DBF_ERROR, + "%s(%s): inactive MPCGROUP - dropped", + CTCM_FUNTAIL, dev->name); + priv->stats.tx_dropped++; + priv->stats.tx_errors++; + priv->stats.tx_carrier_errors++; + goto done; + } + + if (ctcm_test_and_set_busy(dev)) { + CTCM_DBF_TEXT_(MPC_ERROR, CTC_DBF_ERROR, + "%s(%s): device busy - dropped", + CTCM_FUNTAIL, dev->name); + dev_kfree_skb_any(skb); + priv->stats.tx_dropped++; + priv->stats.tx_errors++; + priv->stats.tx_carrier_errors++; + fsm_event(grp->fsm, MPCG_EVENT_INOP, dev); + goto done; + } + + netif_trans_update(dev); + if (ctcmpc_transmit_skb(priv->channel[CTCM_WRITE], skb) != 0) { + CTCM_DBF_TEXT_(MPC_ERROR, CTC_DBF_ERROR, + "%s(%s): device error - dropped", + CTCM_FUNTAIL, dev->name); + dev_kfree_skb_any(skb); + priv->stats.tx_dropped++; + priv->stats.tx_errors++; + priv->stats.tx_carrier_errors++; + ctcm_clear_busy(dev); + fsm_event(grp->fsm, MPCG_EVENT_INOP, dev); + goto done; + } + ctcm_clear_busy(dev); +done: + if (do_debug) + MPC_DBF_DEV_NAME(TRACE, dev, "exit"); + + return NETDEV_TX_OK; /* handle freeing of skb here */ +} + + +/* + * Sets MTU of an interface. + * + * dev Pointer to interface struct. + * new_mtu The new MTU to use for this interface. + * + * returns 0 on success, -EINVAL if MTU is out of valid range. + * (valid range is 576 .. 65527). If VM is on the + * remote side, maximum MTU is 32760, however this is + * not checked here. + */ +static int ctcm_change_mtu(struct net_device *dev, int new_mtu) +{ + struct ctcm_priv *priv; + int max_bufsize; + + priv = dev->ml_priv; + max_bufsize = priv->channel[CTCM_READ]->max_bufsize; + + if (IS_MPC(priv)) { + if (new_mtu > max_bufsize - TH_HEADER_LENGTH) + return -EINVAL; + dev->hard_header_len = TH_HEADER_LENGTH + PDU_HEADER_LENGTH; + } else { + if (new_mtu > max_bufsize - LL_HEADER_LENGTH - 2) + return -EINVAL; + dev->hard_header_len = LL_HEADER_LENGTH + 2; + } + dev->mtu = new_mtu; + return 0; +} + +/* + * Returns interface statistics of a device. + * + * dev Pointer to interface struct. + * + * returns Pointer to stats struct of this interface. + */ +static struct net_device_stats *ctcm_stats(struct net_device *dev) +{ + return &((struct ctcm_priv *)dev->ml_priv)->stats; +} + +static void ctcm_free_netdevice(struct net_device *dev) +{ + struct ctcm_priv *priv; + struct mpc_group *grp; + + CTCM_DBF_TEXT_(SETUP, CTC_DBF_INFO, + "%s(%s)", CTCM_FUNTAIL, dev->name); + priv = dev->ml_priv; + if (priv) { + grp = priv->mpcg; + if (grp) { + if (grp->fsm) + kfree_fsm(grp->fsm); + dev_kfree_skb(grp->xid_skb); + dev_kfree_skb(grp->rcvd_xid_skb); + tasklet_kill(&grp->mpc_tasklet2); + kfree(grp); + priv->mpcg = NULL; + } + if (priv->fsm) { + kfree_fsm(priv->fsm); + priv->fsm = NULL; + } + kfree(priv->xid); + priv->xid = NULL; + /* + * Note: kfree(priv); is done in "opposite" function of + * allocator function probe_device which is remove_device. + */ + } +#ifdef MODULE + free_netdev(dev); +#endif +} + +struct mpc_group *ctcmpc_init_mpc_group(struct ctcm_priv *priv); + +static const struct net_device_ops ctcm_netdev_ops = { + .ndo_open = ctcm_open, + .ndo_stop = ctcm_close, + .ndo_get_stats = ctcm_stats, + .ndo_change_mtu = ctcm_change_mtu, + .ndo_start_xmit = ctcm_tx, +}; + +static const struct net_device_ops ctcm_mpc_netdev_ops = { + .ndo_open = ctcm_open, + .ndo_stop = ctcm_close, + .ndo_get_stats = ctcm_stats, + .ndo_change_mtu = ctcm_change_mtu, + .ndo_start_xmit = ctcmpc_tx, +}; + +static void ctcm_dev_setup(struct net_device *dev) +{ + dev->type = ARPHRD_SLIP; + dev->tx_queue_len = 100; + dev->flags = IFF_POINTOPOINT | IFF_NOARP; + dev->min_mtu = 576; + dev->max_mtu = 65527; +} + +/* + * Initialize everything of the net device except the name and the + * channel structs. + */ +static struct net_device *ctcm_init_netdevice(struct ctcm_priv *priv) +{ + struct net_device *dev; + struct mpc_group *grp; + if (!priv) + return NULL; + + if (IS_MPC(priv)) + dev = alloc_netdev(0, MPC_DEVICE_GENE, NET_NAME_UNKNOWN, + ctcm_dev_setup); + else + dev = alloc_netdev(0, CTC_DEVICE_GENE, NET_NAME_UNKNOWN, + ctcm_dev_setup); + + if (!dev) { + CTCM_DBF_TEXT_(ERROR, CTC_DBF_CRIT, + "%s: MEMORY allocation ERROR", + CTCM_FUNTAIL); + return NULL; + } + dev->ml_priv = priv; + priv->fsm = init_fsm("ctcmdev", dev_state_names, dev_event_names, + CTCM_NR_DEV_STATES, CTCM_NR_DEV_EVENTS, + dev_fsm, dev_fsm_len, GFP_KERNEL); + if (priv->fsm == NULL) { + CTCMY_DBF_DEV(SETUP, dev, "init_fsm error"); + free_netdev(dev); + return NULL; + } + fsm_newstate(priv->fsm, DEV_STATE_STOPPED); + fsm_settimer(priv->fsm, &priv->restart_timer); + + if (IS_MPC(priv)) { + /* MPC Group Initializations */ + grp = ctcmpc_init_mpc_group(priv); + if (grp == NULL) { + MPC_DBF_DEV(SETUP, dev, "init_mpc_group error"); + free_netdev(dev); + return NULL; + } + tasklet_init(&grp->mpc_tasklet2, + mpc_group_ready, (unsigned long)dev); + dev->mtu = MPC_BUFSIZE_DEFAULT - + TH_HEADER_LENGTH - PDU_HEADER_LENGTH; + + dev->netdev_ops = &ctcm_mpc_netdev_ops; + dev->hard_header_len = TH_HEADER_LENGTH + PDU_HEADER_LENGTH; + priv->buffer_size = MPC_BUFSIZE_DEFAULT; + } else { + dev->mtu = CTCM_BUFSIZE_DEFAULT - LL_HEADER_LENGTH - 2; + dev->netdev_ops = &ctcm_netdev_ops; + dev->hard_header_len = LL_HEADER_LENGTH + 2; + } + + CTCMY_DBF_DEV(SETUP, dev, "finished"); + + return dev; +} + +/* + * Main IRQ handler. + * + * cdev The ccw_device the interrupt is for. + * intparm interruption parameter. + * irb interruption response block. + */ +static void ctcm_irq_handler(struct ccw_device *cdev, + unsigned long intparm, struct irb *irb) +{ + struct channel *ch; + struct net_device *dev; + struct ctcm_priv *priv; + struct ccwgroup_device *cgdev; + int cstat; + int dstat; + + CTCM_DBF_TEXT_(TRACE, CTC_DBF_DEBUG, + "Enter %s(%s)", CTCM_FUNTAIL, dev_name(&cdev->dev)); + + if (ctcm_check_irb_error(cdev, irb)) + return; + + cgdev = dev_get_drvdata(&cdev->dev); + + cstat = irb->scsw.cmd.cstat; + dstat = irb->scsw.cmd.dstat; + + /* Check for unsolicited interrupts. */ + if (cgdev == NULL) { + CTCM_DBF_TEXT_(TRACE, CTC_DBF_ERROR, + "%s(%s) unsolicited irq: c-%02x d-%02x\n", + CTCM_FUNTAIL, dev_name(&cdev->dev), cstat, dstat); + dev_warn(&cdev->dev, + "The adapter received a non-specific IRQ\n"); + return; + } + + priv = dev_get_drvdata(&cgdev->dev); + + /* Try to extract channel from driver data. */ + if (priv->channel[CTCM_READ]->cdev == cdev) + ch = priv->channel[CTCM_READ]; + else if (priv->channel[CTCM_WRITE]->cdev == cdev) + ch = priv->channel[CTCM_WRITE]; + else { + dev_err(&cdev->dev, + "%s: Internal error: Can't determine channel for " + "interrupt device %s\n", + __func__, dev_name(&cdev->dev)); + /* Explain: inconsistent internal structures */ + return; + } + + dev = ch->netdev; + if (dev == NULL) { + dev_err(&cdev->dev, + "%s Internal error: net_device is NULL, ch = 0x%p\n", + __func__, ch); + /* Explain: inconsistent internal structures */ + return; + } + + /* Copy interruption response block. */ + memcpy(ch->irb, irb, sizeof(struct irb)); + + /* Issue error message and return on subchannel error code */ + if (irb->scsw.cmd.cstat) { + fsm_event(ch->fsm, CTC_EVENT_SC_UNKNOWN, ch); + CTCM_DBF_TEXT_(TRACE, CTC_DBF_WARN, + "%s(%s): sub-ch check %s: cs=%02x ds=%02x", + CTCM_FUNTAIL, dev->name, ch->id, cstat, dstat); + dev_warn(&cdev->dev, + "A check occurred on the subchannel\n"); + return; + } + + /* Check the reason-code of a unit check */ + if (irb->scsw.cmd.dstat & DEV_STAT_UNIT_CHECK) { + if ((irb->ecw[0] & ch->sense_rc) == 0) + /* print it only once */ + CTCM_DBF_TEXT_(TRACE, CTC_DBF_WARN, + "%s(%s): sense=%02x, ds=%02x", + CTCM_FUNTAIL, ch->id, irb->ecw[0], dstat); + ccw_unit_check(ch, irb->ecw[0]); + return; + } + if (irb->scsw.cmd.dstat & DEV_STAT_BUSY) { + if (irb->scsw.cmd.dstat & DEV_STAT_ATTENTION) + fsm_event(ch->fsm, CTC_EVENT_ATTNBUSY, ch); + else + fsm_event(ch->fsm, CTC_EVENT_BUSY, ch); + return; + } + if (irb->scsw.cmd.dstat & DEV_STAT_ATTENTION) { + fsm_event(ch->fsm, CTC_EVENT_ATTN, ch); + return; + } + if ((irb->scsw.cmd.stctl & SCSW_STCTL_SEC_STATUS) || + (irb->scsw.cmd.stctl == SCSW_STCTL_STATUS_PEND) || + (irb->scsw.cmd.stctl == + (SCSW_STCTL_ALERT_STATUS | SCSW_STCTL_STATUS_PEND))) + fsm_event(ch->fsm, CTC_EVENT_FINSTAT, ch); + else + fsm_event(ch->fsm, CTC_EVENT_IRQ, ch); + +} + +static const struct device_type ctcm_devtype = { + .name = "ctcm", + .groups = ctcm_attr_groups, +}; + +/* + * Add ctcm specific attributes. + * Add ctcm private data. + * + * cgdev pointer to ccwgroup_device just added + * + * returns 0 on success, !0 on failure. + */ +static int ctcm_probe_device(struct ccwgroup_device *cgdev) +{ + struct ctcm_priv *priv; + + CTCM_DBF_TEXT_(SETUP, CTC_DBF_INFO, + "%s %p", + __func__, cgdev); + + if (!get_device(&cgdev->dev)) + return -ENODEV; + + priv = kzalloc(sizeof(struct ctcm_priv), GFP_KERNEL); + if (!priv) { + CTCM_DBF_TEXT_(ERROR, CTC_DBF_ERROR, + "%s: memory allocation failure", + CTCM_FUNTAIL); + put_device(&cgdev->dev); + return -ENOMEM; + } + priv->buffer_size = CTCM_BUFSIZE_DEFAULT; + cgdev->cdev[0]->handler = ctcm_irq_handler; + cgdev->cdev[1]->handler = ctcm_irq_handler; + dev_set_drvdata(&cgdev->dev, priv); + cgdev->dev.type = &ctcm_devtype; + + return 0; +} + +/* + * Add a new channel to the list of channels. + * Keeps the channel list sorted. + * + * cdev The ccw_device to be added. + * type The type class of the new channel. + * priv Points to the private data of the ccwgroup_device. + * + * returns 0 on success, !0 on error. + */ +static int add_channel(struct ccw_device *cdev, enum ctcm_channel_types type, + struct ctcm_priv *priv) +{ + struct channel **c = &channels; + struct channel *ch; + int ccw_num; + int rc = 0; + + CTCM_DBF_TEXT_(SETUP, CTC_DBF_INFO, + "%s(%s), type %d, proto %d", + __func__, dev_name(&cdev->dev), type, priv->protocol); + + ch = kzalloc(sizeof(struct channel), GFP_KERNEL); + if (ch == NULL) + return -ENOMEM; + + ch->protocol = priv->protocol; + if (IS_MPC(priv)) { + ch->discontact_th = kzalloc(TH_HEADER_LENGTH, GFP_KERNEL); + if (ch->discontact_th == NULL) + goto nomem_return; + + ch->discontact_th->th_blk_flag = TH_DISCONTACT; + tasklet_init(&ch->ch_disc_tasklet, + mpc_action_send_discontact, (unsigned long)ch); + + tasklet_init(&ch->ch_tasklet, ctcmpc_bh, (unsigned long)ch); + ch->max_bufsize = (MPC_BUFSIZE_DEFAULT - 35); + ccw_num = 17; + } else + ccw_num = 8; + + ch->ccw = kcalloc(ccw_num, sizeof(struct ccw1), GFP_KERNEL | GFP_DMA); + if (ch->ccw == NULL) + goto nomem_return; + + ch->cdev = cdev; + snprintf(ch->id, CTCM_ID_SIZE, "ch-%s", dev_name(&cdev->dev)); + ch->type = type; + + /* + * "static" ccws are used in the following way: + * + * ccw[0..2] (Channel program for generic I/O): + * 0: prepare + * 1: read or write (depending on direction) with fixed + * buffer (idal allocated once when buffer is allocated) + * 2: nop + * ccw[3..5] (Channel program for direct write of packets) + * 3: prepare + * 4: write (idal allocated on every write). + * 5: nop + * ccw[6..7] (Channel program for initial channel setup): + * 6: set extended mode + * 7: nop + * + * ch->ccw[0..5] are initialized in ch_action_start because + * the channel's direction is yet unknown here. + * + * ccws used for xid2 negotiations + * ch-ccw[8-14] need to be used for the XID exchange either + * X side XID2 Processing + * 8: write control + * 9: write th + * 10: write XID + * 11: read th from secondary + * 12: read XID from secondary + * 13: read 4 byte ID + * 14: nop + * Y side XID Processing + * 8: sense + * 9: read th + * 10: read XID + * 11: write th + * 12: write XID + * 13: write 4 byte ID + * 14: nop + * + * ccws used for double noop due to VM timing issues + * which result in unrecoverable Busy on channel + * 15: nop + * 16: nop + */ + ch->ccw[6].cmd_code = CCW_CMD_SET_EXTENDED; + ch->ccw[6].flags = CCW_FLAG_SLI; + + ch->ccw[7].cmd_code = CCW_CMD_NOOP; + ch->ccw[7].flags = CCW_FLAG_SLI; + + if (IS_MPC(priv)) { + ch->ccw[15].cmd_code = CCW_CMD_WRITE; + ch->ccw[15].flags = CCW_FLAG_SLI | CCW_FLAG_CC; + ch->ccw[15].count = TH_HEADER_LENGTH; + ch->ccw[15].cda = virt_to_phys(ch->discontact_th); + + ch->ccw[16].cmd_code = CCW_CMD_NOOP; + ch->ccw[16].flags = CCW_FLAG_SLI; + + ch->fsm = init_fsm(ch->id, ctc_ch_state_names, + ctc_ch_event_names, CTC_MPC_NR_STATES, + CTC_MPC_NR_EVENTS, ctcmpc_ch_fsm, + mpc_ch_fsm_len, GFP_KERNEL); + } else { + ch->fsm = init_fsm(ch->id, ctc_ch_state_names, + ctc_ch_event_names, CTC_NR_STATES, + CTC_NR_EVENTS, ch_fsm, + ch_fsm_len, GFP_KERNEL); + } + if (ch->fsm == NULL) + goto nomem_return; + + fsm_newstate(ch->fsm, CTC_STATE_IDLE); + + ch->irb = kzalloc(sizeof(struct irb), GFP_KERNEL); + if (ch->irb == NULL) + goto nomem_return; + + while (*c && ctcm_less_than((*c)->id, ch->id)) + c = &(*c)->next; + + if (*c && (!strncmp((*c)->id, ch->id, CTCM_ID_SIZE))) { + CTCM_DBF_TEXT_(SETUP, CTC_DBF_INFO, + "%s (%s) already in list, using old entry", + __func__, (*c)->id); + + goto free_return; + } + + spin_lock_init(&ch->collect_lock); + + fsm_settimer(ch->fsm, &ch->timer); + skb_queue_head_init(&ch->io_queue); + skb_queue_head_init(&ch->collect_queue); + + if (IS_MPC(priv)) { + fsm_settimer(ch->fsm, &ch->sweep_timer); + skb_queue_head_init(&ch->sweep_queue); + } + ch->next = *c; + *c = ch; + return 0; + +nomem_return: + rc = -ENOMEM; + +free_return: /* note that all channel pointers are 0 or valid */ + kfree(ch->ccw); + kfree(ch->discontact_th); + kfree_fsm(ch->fsm); + kfree(ch->irb); + kfree(ch); + return rc; +} + +/* + * Return type of a detected device. + */ +static enum ctcm_channel_types get_channel_type(struct ccw_device_id *id) +{ + enum ctcm_channel_types type; + type = (enum ctcm_channel_types)id->driver_info; + + if (type == ctcm_channel_type_ficon) + type = ctcm_channel_type_escon; + + return type; +} + +/* + * + * Setup an interface. + * + * cgdev Device to be setup. + * + * returns 0 on success, !0 on failure. + */ +static int ctcm_new_device(struct ccwgroup_device *cgdev) +{ + char read_id[CTCM_ID_SIZE]; + char write_id[CTCM_ID_SIZE]; + int direction; + enum ctcm_channel_types type; + struct ctcm_priv *priv; + struct net_device *dev; + struct ccw_device *cdev0; + struct ccw_device *cdev1; + struct channel *readc; + struct channel *writec; + int ret; + int result; + + priv = dev_get_drvdata(&cgdev->dev); + if (!priv) { + result = -ENODEV; + goto out_err_result; + } + + cdev0 = cgdev->cdev[0]; + cdev1 = cgdev->cdev[1]; + + type = get_channel_type(&cdev0->id); + + snprintf(read_id, CTCM_ID_SIZE, "ch-%s", dev_name(&cdev0->dev)); + snprintf(write_id, CTCM_ID_SIZE, "ch-%s", dev_name(&cdev1->dev)); + + ret = add_channel(cdev0, type, priv); + if (ret) { + result = ret; + goto out_err_result; + } + ret = add_channel(cdev1, type, priv); + if (ret) { + result = ret; + goto out_remove_channel1; + } + + ret = ccw_device_set_online(cdev0); + if (ret != 0) { + CTCM_DBF_TEXT_(TRACE, CTC_DBF_NOTICE, + "%s(%s) set_online rc=%d", + CTCM_FUNTAIL, read_id, ret); + result = -EIO; + goto out_remove_channel2; + } + + ret = ccw_device_set_online(cdev1); + if (ret != 0) { + CTCM_DBF_TEXT_(TRACE, CTC_DBF_NOTICE, + "%s(%s) set_online rc=%d", + CTCM_FUNTAIL, write_id, ret); + + result = -EIO; + goto out_ccw1; + } + + dev = ctcm_init_netdevice(priv); + if (dev == NULL) { + result = -ENODEV; + goto out_ccw2; + } + + for (direction = CTCM_READ; direction <= CTCM_WRITE; direction++) { + priv->channel[direction] = + channel_get(type, direction == CTCM_READ ? + read_id : write_id, direction); + if (priv->channel[direction] == NULL) { + if (direction == CTCM_WRITE) + channel_free(priv->channel[CTCM_READ]); + result = -ENODEV; + goto out_dev; + } + priv->channel[direction]->netdev = dev; + priv->channel[direction]->protocol = priv->protocol; + priv->channel[direction]->max_bufsize = priv->buffer_size; + } + /* sysfs magic */ + SET_NETDEV_DEV(dev, &cgdev->dev); + + if (register_netdev(dev)) { + result = -ENODEV; + goto out_dev; + } + + strscpy(priv->fsm->name, dev->name, sizeof(priv->fsm->name)); + + dev_info(&dev->dev, + "setup OK : r/w = %s/%s, protocol : %d\n", + priv->channel[CTCM_READ]->id, + priv->channel[CTCM_WRITE]->id, priv->protocol); + + CTCM_DBF_TEXT_(SETUP, CTC_DBF_INFO, + "setup(%s) OK : r/w = %s/%s, protocol : %d", dev->name, + priv->channel[CTCM_READ]->id, + priv->channel[CTCM_WRITE]->id, priv->protocol); + + return 0; +out_dev: + ctcm_free_netdevice(dev); +out_ccw2: + ccw_device_set_offline(cgdev->cdev[1]); +out_ccw1: + ccw_device_set_offline(cgdev->cdev[0]); +out_remove_channel2: + readc = channel_get(type, read_id, CTCM_READ); + channel_remove(readc); +out_remove_channel1: + writec = channel_get(type, write_id, CTCM_WRITE); + channel_remove(writec); +out_err_result: + return result; +} + +/* + * Shutdown an interface. + * + * cgdev Device to be shut down. + * + * returns 0 on success, !0 on failure. + */ +static int ctcm_shutdown_device(struct ccwgroup_device *cgdev) +{ + struct ctcm_priv *priv; + struct net_device *dev; + + priv = dev_get_drvdata(&cgdev->dev); + if (!priv) + return -ENODEV; + + if (priv->channel[CTCM_READ]) { + dev = priv->channel[CTCM_READ]->netdev; + CTCM_DBF_DEV(SETUP, dev, ""); + /* Close the device */ + ctcm_close(dev); + dev->flags &= ~IFF_RUNNING; + channel_free(priv->channel[CTCM_READ]); + } else + dev = NULL; + + if (priv->channel[CTCM_WRITE]) + channel_free(priv->channel[CTCM_WRITE]); + + if (dev) { + unregister_netdev(dev); + ctcm_free_netdevice(dev); + } + + if (priv->fsm) + kfree_fsm(priv->fsm); + + ccw_device_set_offline(cgdev->cdev[1]); + ccw_device_set_offline(cgdev->cdev[0]); + channel_remove(priv->channel[CTCM_READ]); + channel_remove(priv->channel[CTCM_WRITE]); + priv->channel[CTCM_READ] = priv->channel[CTCM_WRITE] = NULL; + + return 0; + +} + + +static void ctcm_remove_device(struct ccwgroup_device *cgdev) +{ + struct ctcm_priv *priv = dev_get_drvdata(&cgdev->dev); + + CTCM_DBF_TEXT_(SETUP, CTC_DBF_INFO, + "removing device %p, proto : %d", + cgdev, priv->protocol); + + if (cgdev->state == CCWGROUP_ONLINE) + ctcm_shutdown_device(cgdev); + dev_set_drvdata(&cgdev->dev, NULL); + kfree(priv); + put_device(&cgdev->dev); +} + +static struct ccw_device_id ctcm_ids[] = { + {CCW_DEVICE(0x3088, 0x08), .driver_info = ctcm_channel_type_parallel}, + {CCW_DEVICE(0x3088, 0x1e), .driver_info = ctcm_channel_type_ficon}, + {CCW_DEVICE(0x3088, 0x1f), .driver_info = ctcm_channel_type_escon}, + {}, +}; +MODULE_DEVICE_TABLE(ccw, ctcm_ids); + +static struct ccw_driver ctcm_ccw_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "ctcm", + }, + .ids = ctcm_ids, + .probe = ccwgroup_probe_ccwdev, + .remove = ccwgroup_remove_ccwdev, + .int_class = IRQIO_CTC, +}; + +static struct ccwgroup_driver ctcm_group_driver = { + .driver = { + .owner = THIS_MODULE, + .name = CTC_DRIVER_NAME, + }, + .ccw_driver = &ctcm_ccw_driver, + .setup = ctcm_probe_device, + .remove = ctcm_remove_device, + .set_online = ctcm_new_device, + .set_offline = ctcm_shutdown_device, +}; + +static ssize_t group_store(struct device_driver *ddrv, const char *buf, + size_t count) +{ + int err; + + err = ccwgroup_create_dev(ctcm_root_dev, &ctcm_group_driver, 2, buf); + return err ? err : count; +} +static DRIVER_ATTR_WO(group); + +static struct attribute *ctcm_drv_attrs[] = { + &driver_attr_group.attr, + NULL, +}; +static struct attribute_group ctcm_drv_attr_group = { + .attrs = ctcm_drv_attrs, +}; +static const struct attribute_group *ctcm_drv_attr_groups[] = { + &ctcm_drv_attr_group, + NULL, +}; + +/* + * Module related routines + */ + +/* + * Prepare to be unloaded. Free IRQ's and release all resources. + * This is called just before this module is unloaded. It is + * not called, if the usage count is !0, so we don't need to check + * for that. + */ +static void __exit ctcm_exit(void) +{ + ccwgroup_driver_unregister(&ctcm_group_driver); + ccw_driver_unregister(&ctcm_ccw_driver); + root_device_unregister(ctcm_root_dev); + ctcm_unregister_dbf_views(); + pr_info("CTCM driver unloaded\n"); +} + +/* + * Print Banner. + */ +static void print_banner(void) +{ + pr_info("CTCM driver initialized\n"); +} + +/* + * Initialize module. + * This is called just after the module is loaded. + * + * returns 0 on success, !0 on error. + */ +static int __init ctcm_init(void) +{ + int ret; + + channels = NULL; + + ret = ctcm_register_dbf_views(); + if (ret) + goto out_err; + ctcm_root_dev = root_device_register("ctcm"); + ret = PTR_ERR_OR_ZERO(ctcm_root_dev); + if (ret) + goto register_err; + ret = ccw_driver_register(&ctcm_ccw_driver); + if (ret) + goto ccw_err; + ctcm_group_driver.driver.groups = ctcm_drv_attr_groups; + ret = ccwgroup_driver_register(&ctcm_group_driver); + if (ret) + goto ccwgroup_err; + print_banner(); + return 0; + +ccwgroup_err: + ccw_driver_unregister(&ctcm_ccw_driver); +ccw_err: + root_device_unregister(ctcm_root_dev); +register_err: + ctcm_unregister_dbf_views(); +out_err: + pr_err("%s / Initializing the ctcm device driver failed, ret = %d\n", + __func__, ret); + return ret; +} + +module_init(ctcm_init); +module_exit(ctcm_exit); + +MODULE_AUTHOR("Peter Tiedemann <ptiedem@de.ibm.com>"); +MODULE_DESCRIPTION("Network driver for S/390 CTC + CTCMPC (SNA)"); +MODULE_LICENSE("GPL"); + |